From a349f96fded142c5ce350d5971335c448e8af00d Mon Sep 17 00:00:00 2001 From: MatDias97 Date: Mon, 27 Jan 2025 09:45:31 +0100 Subject: [PATCH 001/317] Adding Siape and Siafi clients --- Makefile | 8 +- {src => airflow}/__init__.py | 0 {src/airflow => airflow/dags}/__init__.py | 0 .../dags => airflow/plugins}/__init__.py | 0 .../plugins/cliente_base.py | 4 +- .../plugins/cliente_contratos.py | 0 .../plugins/cliente_estrutura.py | 0 .../plugins/cliente_postgres.py | 0 airflow/plugins/cliente_siafi.py | 90 ++ airflow/plugins/cliente_siape.py | 71 + {src/airflow/plugins => dbt}/__init__.py | 0 {src/dbt => jupyter}/__init__.py | 0 jupyter/test_apis.ipynb | 1252 +++++++++++++++++ pyproject.toml | 6 +- requirements.txt | 32 +- src/superset/__init__.py | 0 {src/jupyter => superset}/__init__.py | 0 17 files changed, 1442 insertions(+), 21 deletions(-) rename {src => airflow}/__init__.py (100%) rename {src/airflow => airflow/dags}/__init__.py (100%) rename {src/airflow/dags => airflow/plugins}/__init__.py (100%) rename {src/airflow => airflow}/plugins/cliente_base.py (92%) rename {src/airflow => airflow}/plugins/cliente_contratos.py (100%) rename {src/airflow => airflow}/plugins/cliente_estrutura.py (100%) rename {src/airflow => airflow}/plugins/cliente_postgres.py (100%) create mode 100644 airflow/plugins/cliente_siafi.py create mode 100644 airflow/plugins/cliente_siape.py rename {src/airflow/plugins => dbt}/__init__.py (100%) rename {src/dbt => jupyter}/__init__.py (100%) create mode 100644 jupyter/test_apis.ipynb delete mode 100644 src/superset/__init__.py rename {src/jupyter => superset}/__init__.py (100%) diff --git a/Makefile b/Makefile index 6e7a0c3c..730ee6ff 100644 --- a/Makefile +++ b/Makefile @@ -10,15 +10,15 @@ setup: format: poetry run black . poetry run ruff check --fix . - poetry run sqlfmt ./src/dbt - poetry run sqlfluff fix ./src/dbt + poetry run sqlfmt ./dbt + poetry run sqlfluff fix ./dbt lint: poetry run black . --check poetry run ruff check . poetry run mypy . - poetry run sqlfmt ./src/dbt --check - poetry run sqlfluff lint ./src/dbt + poetry run sqlfmt ./dbt --check + poetry run sqlfluff lint ./dbt test: poetry run pytest tests diff --git a/src/__init__.py b/airflow/__init__.py similarity index 100% rename from src/__init__.py rename to airflow/__init__.py diff --git a/src/airflow/__init__.py b/airflow/dags/__init__.py similarity index 100% rename from src/airflow/__init__.py rename to airflow/dags/__init__.py diff --git a/src/airflow/dags/__init__.py b/airflow/plugins/__init__.py similarity index 100% rename from src/airflow/dags/__init__.py rename to airflow/plugins/__init__.py diff --git a/src/airflow/plugins/cliente_base.py b/airflow/plugins/cliente_base.py similarity index 92% rename from src/airflow/plugins/cliente_base.py rename to airflow/plugins/cliente_base.py index a0892050..76afe905 100644 --- a/src/airflow/plugins/cliente_base.py +++ b/airflow/plugins/cliente_base.py @@ -13,9 +13,9 @@ class ClienteBase(object): DEFAULT_SLEEP_SECONDS = 1 DEFAULT_TIMEOUT = 30 - def __init__(self, base_url: str) -> None: + def __init__(self, base_url: str, **kwargs: Optional[dict]) -> None: self.base_url = base_url - self.client = httpx.Client(base_url=base_url) + self.client = httpx.Client(base_url=base_url, **kwargs) def request( self, method: http.HTTPMethod, path: str, **kwargs: Any diff --git a/src/airflow/plugins/cliente_contratos.py b/airflow/plugins/cliente_contratos.py similarity index 100% rename from src/airflow/plugins/cliente_contratos.py rename to airflow/plugins/cliente_contratos.py diff --git a/src/airflow/plugins/cliente_estrutura.py b/airflow/plugins/cliente_estrutura.py similarity index 100% rename from src/airflow/plugins/cliente_estrutura.py rename to airflow/plugins/cliente_estrutura.py diff --git a/src/airflow/plugins/cliente_postgres.py b/airflow/plugins/cliente_postgres.py similarity index 100% rename from src/airflow/plugins/cliente_postgres.py rename to airflow/plugins/cliente_postgres.py diff --git a/airflow/plugins/cliente_siafi.py b/airflow/plugins/cliente_siafi.py new file mode 100644 index 00000000..15129b13 --- /dev/null +++ b/airflow/plugins/cliente_siafi.py @@ -0,0 +1,90 @@ +import json +import logging +from typing import Any +import requests +from httpx import HTTPStatusError + +from .cliente_base import ClienteBase + + +class ClienteSiafi(ClienteBase): + + BEARER_ENDPOINT = "https://gateway.apiserpro.serpro.gov.br/token" + SIAFI_ENDPOINT = "https://gateway.apiserpro.serpro.gov.br/api-integra-siafi/api/v2" + + def __init__( + self, bearer_key: str, bearer_secret: str, siafi_credential: str + ) -> None: + headers = ClienteSiafi._setup_headers( + ClienteSiafi.BEARER_ENDPOINT, bearer_key, bearer_secret, siafi_credential + ) + super().__init__(base_url=ClienteSiafi.SIAFI_ENDPOINT, headers=headers) + + @staticmethod + def _get_token(url: str, consumer_key: str, consumer_secret: str) -> str: + """ + Gets token from token endpoint. + + Args: + url: Token endpoint. + consumer_key: Consumer key. + consumer_secret: Consumer secret. + + Returns: + str: The token. + """ + response = requests.post( + url, + data={"grant_type": "client_credentials"}, + auth=(consumer_key, consumer_secret), + ) + data = response.json() + return str(data.get("access_token", "")) + + @staticmethod + def _setup_headers( + bearer_endpoint: str, bearer_key: str, bearer_secret: str, siafi_credential: str + ) -> dict: + """ + Setups the headers for the client. + + Args: + bearer_endpoint: Bearer endpoint. + bearer_key: Bearer key. + bearer_secret: Bearer secret. + siafi_credential: Credencial SIAFI. + + Returns: + dict: The headers. + """ + bearer_token = ClienteSiafi._get_token( + url=bearer_endpoint, consumer_key=bearer_key, consumer_secret=bearer_secret + ) + headers = { + "Authorization": f"Bearer {bearer_token}", + "Content-Type": "application/json", + "x-credencial": siafi_credential, + } + return headers + + def get(self, endpoint: str) -> Any: + """ + Makes a GET request to the siafi endpoint. + + Args: + endpoint: The endpoint. + + Returns: + Any: The response data. + """ + + try: + response = self.client.get(url=endpoint) + response.raise_for_status() + data = response.json() + return data + except HTTPStatusError as http_err: + logging.error(f"HTTP error occurred: {http_err}") + except json.decoder.JSONDecodeError: + logging.warning(f"Could not decode response from {endpoint}") + return None diff --git a/airflow/plugins/cliente_siape.py b/airflow/plugins/cliente_siape.py new file mode 100644 index 00000000..9d9813d7 --- /dev/null +++ b/airflow/plugins/cliente_siape.py @@ -0,0 +1,71 @@ +from typing import Dict, Any +from requests import Session +import requests + +from zeep import Transport, Client + + +class ClienteSiape(object): + + BEARER_ENDPOINT = ( + "https://apigateway.conectagov.estaleiro.serpro.gov.br/oauth2/jwt-token/" + ) + SOAP_ENDPOINT = "https://apigateway.conectagov.estaleiro.serpro.gov.br/api-consulta-siape/v1/consulta-siape" + + def __init__(self, oauth_username: str, oauth_password: str) -> None: + token = ClienteSiape._get_token(oauth_username, oauth_password) + headers = ClienteSiape._get_headers(token) + session = Session() + session.headers.update(headers) + transport = Transport(session=session) + self.base_url = ClienteSiape.SOAP_ENDPOINT + self.client = Client(ClienteSiape.SOAP_ENDPOINT, transport=transport) + + @staticmethod + def _get_token(oauth_username: str, oauth_password: str) -> str: + """ + Gets the token for the client. + + Args: + oauth_username (str): The OAuth username. + oauth_password (str): The OAuth password. + + Returns: + str: The token. + """ + auth_response = requests.get( + ClienteSiape.BEARER_ENDPOINT, + auth=(oauth_username, oauth_password), + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + auth_json = auth_response.json() + return str(auth_json.get("access_token", "")) + + @staticmethod + def _get_headers(token: str) -> Dict[str, str]: + """ + Builds the headers for the client. + + Args: + token (str): The OAuth token. + + Returns: + Dict[str, str]: The headers. + """ + return {"Authorization": f"Bearer {token}"} + + def method(self, method_name: str) -> Any: + """ + Applies the method to the client. + + Args: + method_name (str): The method name. + + Returns: + Any: The response. + """ + try: + response = getattr(self.client.service, method_name) + return response + except Exception as e: + raise Exception(f"Error making SOAP request: {str(e)}") diff --git a/src/airflow/plugins/__init__.py b/dbt/__init__.py similarity index 100% rename from src/airflow/plugins/__init__.py rename to dbt/__init__.py diff --git a/src/dbt/__init__.py b/jupyter/__init__.py similarity index 100% rename from src/dbt/__init__.py rename to jupyter/__init__.py diff --git a/jupyter/test_apis.ipynb b/jupyter/test_apis.ipynb new file mode 100644 index 00000000..d80097fb --- /dev/null +++ b/jupyter/test_apis.ipynb @@ -0,0 +1,1252 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 48, + "id": "initial_id", + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-09T20:09:37.850465Z", + "start_time": "2025-01-09T20:09:35.741919Z" + }, + "collapsed": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[\u001B[34m2025-01-09T21:09:37.836+0100\u001B[0m] {\u001B[34m_client.py:\u001B[0m1025} INFO\u001B[0m - HTTP Request: GET https://estruturaorganizacional.dados.gov.br/doc/estrutura-organizacional/resumida?codigoPoder=1&codigoEsfera=1&codigoUnidade=7 \"HTTP/1.1 200 OK\"\u001B[0m\n" + ] + } + ], + "source": [ + "import sys\n", + "sys.path.append('../src')\n", + "\n", + "from airflow.clients.cliente_estrutura import ClienteEstrutura\n", + "client = ClienteEstrutura()\n", + "response = client.get_estrutura_organizacional_resumida(codigo_unidade='7', codigo_poder='1', codigo_esfera='1')" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "11862578f62a2c86", + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-09T20:09:38.644588Z", + "start_time": "2025-01-09T20:09:38.641016Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "90" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(response[\"unidades\"])" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "604a2fc324e1fbdf", + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-09T19:54:22.563588Z", + "start_time": "2025-01-09T19:54:22.303994Z" + } + }, + "outputs": [], + "source": [ + "from typing import Any, Dict, List\n", + "import json\n", + "\n", + "def flatten(data: Dict[str, Any], parent_key: str = \"\", sep: str = \"_\") -> Dict[str, Any]:\n", + " \"\"\"Flatten nested dictionaries.\n", + "\n", + " Args:\n", + " data: Dictionary to flatten\n", + " parent_key: Key from parent dictionary\n", + " sep: Separator between nested keys\n", + "\n", + " Returns:\n", + " Flattened dictionary\n", + " \"\"\"\n", + " items: List[tuple[str, Any]] = []\n", + " for key, value in data.items():\n", + " new_key = f\"{parent_key}{sep}{key}\" if parent_key else key\n", + "\n", + " if isinstance(value, dict):\n", + " items.extend(flatten(value, new_key, sep=sep).items())\n", + " elif isinstance(value, list):\n", + " items.append((new_key, json.dumps(value)))\n", + " else:\n", + " items.append((new_key, value))\n", + "\n", + " return dict(items)\n", + "\n", + "flattened_test = flatten(response, sep=\"_\")" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "c01a90398d878fb7", + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-09T19:55:46.261025Z", + "start_time": "2025-01-09T19:55:46.257853Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "str" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(flattened_test[\"unidades\"])" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "666607bcc9453bba", + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-09T19:29:57.794235Z", + "start_time": "2025-01-09T19:29:57.783063Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'codigoUnidade': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/7', 'codigoUnidadePai': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/308804', 'codigoOrgaoEntidade': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/7', 'codigoTipoUnidade': 'https://estruturaorganizacional.dados.gov.br/id/tipo-unidade/entidade', 'nome': 'Instituto de Pesquisa Econômica Aplicada', 'sigla': 'IPEA', 'codigoEsfera': 'https://estruturaorganizacional.dados.gov.br/id/esfera/1', 'codigoPoder': 'https://estruturaorganizacional.dados.gov.br/id/poder/1', 'codigoNaturezaJuridica': 'https://estruturaorganizacional.dados.gov.br/id/natureza-juridica/2', 'codigoSubNaturezaJuridica': None, 'nivelNormatizacao': 'LEI_DECRETO', 'versaoConsulta': '114.0.0', 'dataInicialVersaoConsulta': '2024-10-08', 'dataFinalVersaoConsulta': None, 'operacao': None, 'codigoUnidadePaiAnterior': None, 'codigoOrgaoEntidadeAnterior': None}\n", + "{'codigoUnidade': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/7656', 'codigoUnidadePai': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/7', 'codigoOrgaoEntidade': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/7', 'codigoTipoUnidade': 'https://estruturaorganizacional.dados.gov.br/id/tipo-unidade/unidade-colegiada', 'nome': 'Conselho Consultivo', 'sigla': 'CC-IPEA', 'codigoEsfera': 'https://estruturaorganizacional.dados.gov.br/id/esfera/1', 'codigoPoder': 'https://estruturaorganizacional.dados.gov.br/id/poder/1', 'codigoNaturezaJuridica': 'https://estruturaorganizacional.dados.gov.br/id/natureza-juridica/2', 'codigoSubNaturezaJuridica': None, 'nivelNormatizacao': 'ATO_INTERNO', 'versaoConsulta': '107.0.191', 'dataInicialVersaoConsulta': '2021-09-24', 'dataFinalVersaoConsulta': None, 'operacao': None, 'codigoUnidadePaiAnterior': None, 'codigoOrgaoEntidadeAnterior': None}\n", + "{'codigoUnidade': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/7628', 'codigoUnidadePai': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/7', 'codigoOrgaoEntidade': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/7', 'codigoTipoUnidade': 'https://estruturaorganizacional.dados.gov.br/id/tipo-unidade/unidade-administrativa', 'nome': 'Gabinete', 'sigla': 'GABIN-IPEA', 'codigoEsfera': 'https://estruturaorganizacional.dados.gov.br/id/esfera/1', 'codigoPoder': 'https://estruturaorganizacional.dados.gov.br/id/poder/1', 'codigoNaturezaJuridica': 'https://estruturaorganizacional.dados.gov.br/id/natureza-juridica/2', 'codigoSubNaturezaJuridica': None, 'nivelNormatizacao': 'LEI_DECRETO', 'versaoConsulta': '113.1.0', 'dataInicialVersaoConsulta': '2024-10-01', 'dataFinalVersaoConsulta': None, 'operacao': None, 'codigoUnidadePaiAnterior': None, 'codigoOrgaoEntidadeAnterior': None}\n", + "{'codigoUnidade': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/255032', 'codigoUnidadePai': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/3561', 'codigoOrgaoEntidade': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/3561', 'codigoTipoUnidade': 'https://estruturaorganizacional.dados.gov.br/id/tipo-unidade/unidade-administrativa', 'nome': 'Diretoria de Políticas de Educação a Distância', 'sigla': 'DIPEAD', 'codigoEsfera': 'https://estruturaorganizacional.dados.gov.br/id/esfera/1', 'codigoPoder': 'https://estruturaorganizacional.dados.gov.br/id/poder/1', 'codigoNaturezaJuridica': 'https://estruturaorganizacional.dados.gov.br/id/natureza-juridica/4', 'codigoSubNaturezaJuridica': 'https://estruturaorganizacional.dados.gov.br/id/subnatureza-juridica/17', 'nivelNormatizacao': 'ATO_INTERNO', 'versaoConsulta': '112.1.0', 'dataInicialVersaoConsulta': '2022-05-25', 'dataFinalVersaoConsulta': None, 'operacao': None, 'codigoUnidadePaiAnterior': None, 'codigoOrgaoEntidadeAnterior': None}\n", + "{'codigoUnidade': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/105288', 'codigoUnidadePai': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/104862', 'codigoOrgaoEntidade': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/100930', 'codigoTipoUnidade': 'https://estruturaorganizacional.dados.gov.br/id/tipo-unidade/unidade-administrativa', 'nome': 'Diretoria de Pesquisa, Extensão e Assistência Estudantil', 'sigla': 'DIPEA', 'codigoEsfera': 'https://estruturaorganizacional.dados.gov.br/id/esfera/1', 'codigoPoder': 'https://estruturaorganizacional.dados.gov.br/id/poder/1', 'codigoNaturezaJuridica': 'https://estruturaorganizacional.dados.gov.br/id/natureza-juridica/4', 'codigoSubNaturezaJuridica': 'https://estruturaorganizacional.dados.gov.br/id/subnatureza-juridica/17', 'nivelNormatizacao': 'ATO_INTERNO', 'versaoConsulta': '114.1.0', 'dataInicialVersaoConsulta': '2022-08-08', 'dataFinalVersaoConsulta': None, 'operacao': None, 'codigoUnidadePaiAnterior': None, 'codigoOrgaoEntidadeAnterior': None}\n", + "{'codigoUnidade': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/440469', 'codigoUnidadePai': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/84156', 'codigoOrgaoEntidade': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/41066', 'codigoTipoUnidade': 'https://estruturaorganizacional.dados.gov.br/id/tipo-unidade/unidade-administrativa', 'nome': 'Instituto de Pesquisas do Exército na Amazônia', 'sigla': 'IPEAM', 'codigoEsfera': 'https://estruturaorganizacional.dados.gov.br/id/esfera/1', 'codigoPoder': 'https://estruturaorganizacional.dados.gov.br/id/poder/1', 'codigoNaturezaJuridica': 'https://estruturaorganizacional.dados.gov.br/id/natureza-juridica/3', 'codigoSubNaturezaJuridica': None, 'nivelNormatizacao': 'ATO_INTERNO', 'versaoConsulta': '311.4.0', 'dataInicialVersaoConsulta': '2024-10-28', 'dataFinalVersaoConsulta': None, 'operacao': None, 'codigoUnidadePaiAnterior': None, 'codigoOrgaoEntidadeAnterior': None}\n" + ] + } + ], + "source": [ + "unidades = response['unidades']\n", + "\n", + "for unidade in unidades:\n", + " if 'IPEA' in unidade[\"sigla\"]:\n", + " print(unidade)" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "57b91f5d0bf94795", + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-09T20:10:27.352101Z", + "start_time": "2025-01-09T20:10:24.833263Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[\u001B[34m2025-01-09T21:10:27.343+0100\u001B[0m] {\u001B[34m_client.py:\u001B[0m1025} INFO\u001B[0m - HTTP Request: GET https://contratos.comprasnet.gov.br/api/contrato/ug/113602 \"HTTP/1.1 200 OK\"\u001B[0m\n" + ] + } + ], + "source": [ + "from airflow.clients.cliente_contratos import ClienteContratos\n", + "client = ClienteContratos()\n", + "response = client.get_contratos_by_ug(ug_code = '113602')" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "5d226ed4294609f4", + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-09T20:14:18.724625Z", + "start_time": "2025-01-09T20:14:18.720065Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'id': 4153,\n", + " 'receita_despesa': 'Despesa',\n", + " 'numero': '00014/2019',\n", + " 'contratante': {'orgao_origem': {'codigo': None,\n", + " 'nome': None,\n", + " 'unidade_gestora_origem': {'codigo': None,\n", + " 'nome_resumido': None,\n", + " 'nome': None,\n", + " 'sisg': 'Não',\n", + " 'utiliza_siafi': 'Não',\n", + " 'utiliza_antecipagov': 'Não'}},\n", + " 'orgao': {'codigo': '61201',\n", + " 'nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA',\n", + " 'unidade_gestora': {'codigo': '113602',\n", + " 'nome_resumido': 'IPEA/RJ',\n", + " 'nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA',\n", + " 'sisg': 'Sim',\n", + " 'utiliza_siafi': 'Sim',\n", + " 'utiliza_antecipagov': 'Sim'}}},\n", + " 'fornecedor': {'tipo': 'JURIDICA',\n", + " 'cnpj_cpf_idgener': '00.000.000/0001-91',\n", + " 'nome': 'BANCO DO BRASIL SA'},\n", + " 'codigo_tipo': '96',\n", + " 'tipo': 'Acordo de Cooperação Técnica (ACT)',\n", + " 'subtipo': None,\n", + " 'prorrogavel': None,\n", + " 'situacao': 'Ativo',\n", + " 'justificativa_inativo': None,\n", + " 'categoria': 'Serviços',\n", + " 'subcategoria': None,\n", + " 'unidades_requisitantes': None,\n", + " 'processo': '03001.002870/2019-10',\n", + " 'objeto': 'O PRESENTE INSTRUMENTO TEM POR OBJETIVO REGULAMENTAR O ESTABELECIMENTO, PELO BANCO, DOS CRITÉRIOS PARA ABERTURA DE DEPÓSITO EM GARANTIA - BLOQUEADO PARA MOVIMENTAÇÃO, DESTINADO A ABRIGAR OS RECURSOS PROVISIONADOS DE RUBRICAS CONSTANTES DA PLANILHA DE CUSTOS E FORMAÇÃO DE PREÇOS DOS CONTRATOS FIRMADOS PELA ADMINISTRAÇÃO PUBLICA FEDERAL, BEM COMO VIABILIZAR O ACESSO DA ADMINISTRAÇÃO PUBLICA FEDERAL AOS SALDOS E EXTRAÍAS DE TODOS OS \"EVENTOS\".',\n", + " 'amparo_legal': '',\n", + " 'informacao_complementar': None,\n", + " 'codigo_modalidade': 'NAOSEAPLIC',\n", + " 'modalidade': 'Não se Aplica',\n", + " 'unidade_compra': None,\n", + " 'licitacao_numero': None,\n", + " 'sistema_origem_licitacao': None,\n", + " 'data_assinatura': '2020-02-27',\n", + " 'data_publicacao': '2020-03-12',\n", + " 'data_proposta_comercial': None,\n", + " 'vigencia_inicio': '2020-02-27',\n", + " 'vigencia_fim': '2025-02-27',\n", + " 'valor_inicial': '0,01',\n", + " 'valor_global': '0,01',\n", + " 'num_parcelas': 1,\n", + " 'valor_parcela': '0,01',\n", + " 'valor_acumulado': '0,57',\n", + " 'links': {'historico': 'https://contratos.comprasnet.gov.br/api/contrato/4153/historico',\n", + " 'empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/4153/empenhos',\n", + " 'cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/4153/cronograma',\n", + " 'garantias': 'https://contratos.comprasnet.gov.br/api/contrato/4153/garantias',\n", + " 'itens': 'https://contratos.comprasnet.gov.br/api/contrato/4153/itens',\n", + " 'prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/4153/prepostos',\n", + " 'responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/4153/responsaveis',\n", + " 'despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/4153/despesas_acessorias',\n", + " 'faturas': 'https://contratos.comprasnet.gov.br/api/contrato/4153/faturas',\n", + " 'ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/4153/ocorrencias',\n", + " 'terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/4153/terceirizados',\n", + " 'arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/4153/arquivos'}}" + ] + }, + "execution_count": 60, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "response[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "adb07b2526a80a19", + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-09T20:19:00.902262Z", + "start_time": "2025-01-09T20:19:00.894996Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[{'id': 4153, 'receita_despesa': 'Despesa', 'numero': '00014/2019', 'codigo_tipo': '96', 'tipo': 'Acordo de Cooperação Técnica (ACT)', 'subtipo': None, 'prorrogavel': None, 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.002870/2019-10', 'objeto': 'O PRESENTE INSTRUMENTO TEM POR OBJETIVO REGULAMENTAR O ESTABELECIMENTO, PELO BANCO, DOS CRITÉRIOS PARA ABERTURA DE DEPÓSITO EM GARANTIA - BLOQUEADO PARA MOVIMENTAÇÃO, DESTINADO A ABRIGAR OS RECURSOS PROVISIONADOS DE RUBRICAS CONSTANTES DA PLANILHA DE CUSTOS E FORMAÇÃO DE PREÇOS DOS CONTRATOS FIRMADOS PELA ADMINISTRAÇÃO PUBLICA FEDERAL, BEM COMO VIABILIZAR O ACESSO DA ADMINISTRAÇÃO PUBLICA FEDERAL AOS SALDOS E EXTRAÍAS DE TODOS OS \"EVENTOS\".', 'amparo_legal': '', 'informacao_complementar': None, 'codigo_modalidade': 'NAOSEAPLIC', 'modalidade': 'Não se Aplica', 'unidade_compra': None, 'licitacao_numero': None, 'sistema_origem_licitacao': None, 'data_assinatura': '2020-02-27', 'data_publicacao': '2020-03-12', 'data_proposta_comercial': None, 'vigencia_inicio': '2020-02-27', 'vigencia_fim': '2025-02-27', 'valor_inicial': '0,01', 'valor_global': '0,01', 'num_parcelas': 1, 'valor_parcela': '0,01', 'valor_acumulado': '0,57', 'contratante_orgao_origem_codigo': None, 'contratante_orgao_origem_nome': None, 'contratante_orgao_origem_unidade_gestora_origem_codigo': None, 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': None, 'contratante_orgao_origem_unidade_gestora_origem_nome': None, 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Não', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Não', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Não', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '00.000.000/0001-91', 'fornecedor_nome': 'BANCO DO BRASIL SA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/4153/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/4153/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/4153/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/4153/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/4153/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/4153/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/4153/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/4153/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/4153/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/4153/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/4153/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/4153/arquivos'}, {'id': 4980, 'receita_despesa': 'Despesa', 'numero': '00014/2019', 'codigo_tipo': '50', 'tipo': 'Contrato', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.003739/2019-70', 'objeto': 'PRESTAÇÃO DE SERVIÇOS DE TRANSPORTE TERRESTRE DE MATERIAIS E CARGAS, CONFORME CONDIÇÕES ESTABELECIDAS NO TERMO DE REFERÊNCIA E EDITAL.', 'amparo_legal': '', 'informacao_complementar': 'ESTE TERMO DE CONTRATO VINCULA-SE AO EDITAL DO PREGÃO N°08/2018 - ATA SRP N°67/2018 - UASG: 443020 - INSTITUTO DE PESQUISAS JARDIM BOTÂNICO - RIO DE JANEIRO, IDENTIFICADO NO PREÂMBULO E À PROPOSTA VENCEDORA, INDEPENDENTEMENTE DE TRANSCRIÇÃO.', 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '443020', 'licitacao_numero': '00008/2018', 'sistema_origem_licitacao': None, 'data_assinatura': '2019-12-03', 'data_publicacao': '2019-12-05', 'data_proposta_comercial': None, 'vigencia_inicio': '2019-12-03', 'vigencia_fim': '2021-12-03', 'valor_inicial': '555.680,00', 'valor_global': '555.680,00', 'num_parcelas': 12, 'valor_parcela': '46.306,67', 'valor_acumulado': '1.071.711,74', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '01.117.975/0001-67', 'fornecedor_nome': 'MAXPESA CONSTRUCOES TRANSPORTES LOCACOES E MONTAGENS LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/4980/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/4980/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/4980/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/4980/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/4980/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/4980/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/4980/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/4980/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/4980/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/4980/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/4980/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/4980/arquivos'}, {'id': 5738, 'receita_despesa': 'Despesa', 'numero': '00001/2020', 'codigo_tipo': '50', 'tipo': 'Contrato', 'subtipo': None, 'prorrogavel': None, 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.000485/2020-71', 'objeto': 'CONTRATAÇÃO DE EMPRESA ESPECIALIZADA PARA O FORNECIMENTO, TRANSPORTE E INSTALAÇÃO DO FORRO MINERAL PARA A UNIDADE DO IPEA/RJ.', 'amparo_legal': '', 'informacao_complementar': 'ESTE CONTRATO É ORIUNDO DA ATA DE REGISTRO DE PREÇOS N.º 072/2019, DERIVADA DO PREGÃO ELETRÔNICO Nº 58/2019, COM O CORRESPONDENTE EDITAL DE LICITAÇÃO E COM A PROPOSTA DA CONTRATADA BERNARDO DE SÁ CONSTRUTORA, INCORPORADORA E EMPREENDIMENTOS IMOBILIÁRIOS LTDA, INSCRITA NO CNPJ N° 09.248.466/0001-85, QUE INDEPENDENTEMENTE DE TRANSCRIÇÃO PASSAM A FAZER PARTE INTEGRANTE E COMPLEMENTAR DO PRESENTE CONTRATO.', 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '080016', 'licitacao_numero': '00072/2019', 'sistema_origem_licitacao': None, 'data_assinatura': '2020-02-27', 'data_publicacao': '2020-03-02', 'data_proposta_comercial': None, 'vigencia_inicio': '2020-02-27', 'vigencia_fim': '2020-06-03', 'valor_inicial': '118.000,00', 'valor_global': '472.000,00', 'num_parcelas': 1, 'valor_parcela': '472.000,00', 'valor_acumulado': '472.000,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '09.248.466/0001-85', 'fornecedor_nome': 'CASA BLANKA CONSTRUTORA E INCORPORADORA LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/5738/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/5738/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/5738/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/5738/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/5738/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/5738/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/5738/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/5738/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/5738/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/5738/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/5738/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/5738/arquivos'}, {'id': 6446, 'receita_despesa': 'Despesa', 'numero': '51257/2019', 'codigo_tipo': '98', 'tipo': 'Outros', 'subtipo': 'TERMO DE CESSÃO DE USO DE ÁREA', 'prorrogavel': None, 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Cessão', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.003714/2019-76', 'objeto': 'TERMO DE CESSÃO DE USO DE ÁREA DISPONÍVEL NO EDIFÍCIO LOCALIZADO NA AV. PRESIDENTE VARGAS, Nº 730, NO RIO DE JANEIRO/RJ, QUE ENTRE SI FAZEM O BANCO CENTRAL DO BRASIL, COMO CEDENTE, E O INSTITUTO DE PESQUISA ECONÔMICA APLICADA, COMO CESSIONÁRIA.', 'amparo_legal': '', 'informacao_complementar': 'TERMO DE CESSÃO DE USO', 'codigo_modalidade': 'NAOSEAPLIC', 'modalidade': 'Não se Aplica', 'unidade_compra': None, 'licitacao_numero': None, 'sistema_origem_licitacao': None, 'data_assinatura': '2019-12-16', 'data_publicacao': '2020-12-17', 'data_proposta_comercial': None, 'vigencia_inicio': '2020-01-02', 'vigencia_fim': '2022-07-01', 'valor_inicial': '0,01', 'valor_global': '0,01', 'num_parcelas': 30, 'valor_parcela': '0,01', 'valor_acumulado': '0,30', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '00.038.166/0010-98', 'fornecedor_nome': 'BANCO CENTRAL DO BRASIL', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/6446/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/6446/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/6446/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/6446/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/6446/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/6446/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/6446/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/6446/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/6446/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/6446/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/6446/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/6446/arquivos'}, {'id': 64894, 'receita_despesa': 'Despesa', 'numero': '2020NE800068', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': None, 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.000701/2020-89', 'objeto': 'CONTRATAÇÃO DE SERVIÇO DE EMPRESA DE ENGENHARIA ESPECIALIZADA NA FABRICAÇÃO E INSTALAÇÃO DE REDE DE DUTOS PARA O CPD DO NOVO ENDEREÇO DO IPEA - UNIDADE DESCENTRALIZADA NO RIO DE JANEIRO/RJ, SITUADO NO EDIFÍCIO DO BANCO CENTRAL .', 'amparo_legal': '', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00004/2020', 'sistema_origem_licitacao': None, 'data_assinatura': '2020-03-10', 'data_publicacao': '2020-04-15', 'data_proposta_comercial': None, 'vigencia_inicio': '2020-03-11', 'vigencia_fim': '2020-05-27', 'valor_inicial': '13.900,00', 'valor_global': '13.900,00', 'num_parcelas': 1, 'valor_parcela': '13.900,00', 'valor_acumulado': '13.900,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '26.669.875/0001-74', 'fornecedor_nome': 'CVAS REFRIGERACAO LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/64894/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/64894/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/64894/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/64894/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/64894/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/64894/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/64894/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/64894/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/64894/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/64894/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/64894/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/64894/arquivos'}, {'id': 64895, 'receita_despesa': 'Despesa', 'numero': '2020NE800121', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': None, 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.001956/2020-69', 'objeto': 'AQUISIÇÃO DE 02 (DUAS) ASSINATURAS DIGITAIS ANUAIS DA REVISTA “CONJUNTURA ECONÔMICA”.', 'amparo_legal': '', 'informacao_complementar': None, 'codigo_modalidade': '07', 'modalidade': 'Inexigibilidade', 'unidade_compra': '113602', 'licitacao_numero': '00003/2020', 'sistema_origem_licitacao': None, 'data_assinatura': '2020-06-29', 'data_publicacao': None, 'data_proposta_comercial': None, 'vigencia_inicio': '2020-07-03', 'vigencia_fim': '2021-07-03', 'valor_inicial': '230,00', 'valor_global': '230,00', 'num_parcelas': 1, 'valor_parcela': '230,00', 'valor_acumulado': '230,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '33.641.663/0001-44', 'fornecedor_nome': 'FUNDACAO GETULIO VARGAS', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/64895/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/64895/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/64895/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/64895/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/64895/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/64895/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/64895/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/64895/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/64895/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/64895/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/64895/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/64895/arquivos'}, {'id': 64902, 'receita_despesa': 'Despesa', 'numero': '2020NE800026', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': None, 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.004691/2019-17', 'objeto': 'AQUISIÇÃO DE EQUIPAMENTOS DE CONTROLE DE ACESSO BIOMÉTRICO E DE SISTEMA DE VÍDEO VIGILÂNCIA POR CIRCUITO INTERNO DE TV (CFTV) PARA A INFRAESTRUTUTA DA NOVA SEDE DO IPEA/RJ.', 'amparo_legal': '', 'informacao_complementar': 'ADESÃO À ATA DO PREGÃO 09/2018 DA UASG 160530', 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '113602', 'licitacao_numero': '00009/2018', 'sistema_origem_licitacao': None, 'data_assinatura': '2020-01-30', 'data_publicacao': '2021-04-14', 'data_proposta_comercial': None, 'vigencia_inicio': '2020-02-06', 'vigencia_fim': '2020-12-07', 'valor_inicial': '26.738,70', 'valor_global': '26.737,14', 'num_parcelas': 1, 'valor_parcela': '26.737,14', 'valor_acumulado': '26.737,14', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '82.901.000/0001-27', 'fornecedor_nome': 'INTELBRAS S.A. INDUSTRIA DE TELECOMUNICACAO ELETRONICA BRASILEIRA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/64902/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/64902/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/64902/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/64902/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/64902/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/64902/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/64902/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/64902/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/64902/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/64902/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/64902/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/64902/arquivos'}, {'id': 88309, 'receita_despesa': 'Despesa', 'numero': '00001/2021', 'codigo_tipo': '50', 'tipo': 'Contrato', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.002352/2020-30', 'objeto': 'CONTRATAÇÃO DE PRODUTOS E SERVIÇOS POR MEIO DE PACOTE\\r\\nDE SERVIÇOS DOS CORREIOS', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 25 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '07', 'modalidade': 'Inexigibilidade', 'unidade_compra': '113602', 'licitacao_numero': '00002/2021', 'sistema_origem_licitacao': None, 'data_assinatura': '2021-01-21', 'data_publicacao': '2021-02-08', 'data_proposta_comercial': None, 'vigencia_inicio': '2021-01-21', 'vigencia_fim': '2026-01-21', 'valor_inicial': '1.718,32', 'valor_global': '1.718,32', 'num_parcelas': 1, 'valor_parcela': '1.718,32', 'valor_acumulado': '1.718,32', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '34.028.316/0002-94', 'fornecedor_nome': 'EMPRESA BRASILEIRA DE CORREIOS E TELEGRAFOS', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/88309/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/88309/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/88309/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/88309/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/88309/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/88309/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/88309/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/88309/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/88309/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/88309/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/88309/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/88309/arquivos'}, {'id': 101463, 'receita_despesa': 'Despesa', 'numero': '2021NE000022', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': None, 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.001229/2021-82', 'objeto': 'RENOVAÇÃO DE CERTIFICADO DIGITAL TOKEN,PARA O CHEFE DO SERVIÇO DE EXECUÇÃO ORÇAMENTÁRIA E FINANCEIRA (SEEOF)-SERVIDOR\\r\\nJORGE ACÁCIO DE A.SILVA.', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 25', 'informacao_complementar': None, 'codigo_modalidade': '07', 'modalidade': 'Inexigibilidade', 'unidade_compra': None, 'licitacao_numero': '00004/2021', 'sistema_origem_licitacao': None, 'data_assinatura': '2021-04-20', 'data_publicacao': '2021-04-30', 'data_proposta_comercial': None, 'vigencia_inicio': '2021-05-04', 'vigencia_fim': '2021-05-07', 'valor_inicial': '0,00', 'valor_global': '206,00', 'num_parcelas': 1, 'valor_parcela': '206,00', 'valor_acumulado': '206,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'UG', 'fornecedor_cnpj_cpf_idgener': '806030', 'fornecedor_nome': 'SERPRO - SEDE - BRASILIA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/101463/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/101463/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/101463/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/101463/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/101463/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/101463/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/101463/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/101463/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/101463/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/101463/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/101463/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/101463/arquivos'}, {'id': 120371, 'receita_despesa': 'Despesa', 'numero': '00003/2021', 'codigo_tipo': '50', 'tipo': 'Contrato', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.000089/2021-25', 'objeto': 'CONTRATAÇÃO DE SERVIÇOS CONTINUADOS DE OUTSOURCING PARA OPERAÇÃO DE ALMOXARIFADO VIRTUAL, SOB DEMANDA, VISANDO AO SUPRIMENTO DE MATERIAIS DE CONSUMO, VIA SISTEMA WEB', 'amparo_legal': 'LEI 10.520 / 2002 - Artigo: 1', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '201057', 'licitacao_numero': '00007/2020', 'sistema_origem_licitacao': None, 'data_assinatura': '2021-10-21', 'data_publicacao': '2021-11-04', 'data_proposta_comercial': None, 'vigencia_inicio': '2021-10-21', 'vigencia_fim': '2024-04-21', 'valor_inicial': '146.539,03', 'valor_global': '163.540,28', 'num_parcelas': 1, 'valor_parcela': '163.540,28', 'valor_acumulado': '6.613.212,86', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '06.698.091/0005-90', 'fornecedor_nome': 'AUTOPEL AUTOMACAO COMERCIAL E INFORMATICA LTDA.', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/120371/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/120371/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/120371/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/120371/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/120371/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/120371/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/120371/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/120371/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/120371/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/120371/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/120371/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/120371/arquivos'}, {'id': 121592, 'receita_despesa': 'Despesa', 'numero': '00014/2019', 'codigo_tipo': '96', 'tipo': 'Acordo de Cooperação Técnica (ACT)', 'subtipo': None, 'prorrogavel': None, 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.002870/2019-10', 'objeto': 'REGULAMENTAR O ESTABELECIMENTO, PELO\\r\\nBANCO, DOS CRITÉRIOS PARA ABERTURA DE DEPÓSITO EM GARANTIA - BLOQUEADO PARA\\r\\nMOVIMENTAÇÃO, DESTINADO A ABRIGAR OS RECURSOS PROVISIONADOS DE RUBRICAS CONSTANTES DA PLANILHA DE CUSTOS E FORMAÇÃO DE PREÇOS DOS CONTRATOS FIRMADOS PELA ADMINISTRAÇÃO PUBLICA FEDERAL, BEM COMO VIABILIZAR O ACESSO DA ADMINISTRAÇÃO PUBLICA\\r\\nFEDERAL AOS SALDOS E EXTRAÍAS DE TODOS OS \"EVENTOS\".', 'amparo_legal': 'NÃO SE APLICA', 'informacao_complementar': None, 'codigo_modalidade': 'NAOSEAPLIC', 'modalidade': 'Não se Aplica', 'unidade_compra': None, 'licitacao_numero': None, 'sistema_origem_licitacao': None, 'data_assinatura': '2020-02-27', 'data_publicacao': '2021-11-12', 'data_proposta_comercial': None, 'vigencia_inicio': '2020-02-27', 'vigencia_fim': '2025-02-27', 'valor_inicial': '0,00', 'valor_global': '0,00', 'num_parcelas': 1, 'valor_parcela': '0,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '00.000.000/0001-91', 'fornecedor_nome': 'BANCO DO BRASIL SA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/121592/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/121592/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/121592/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/121592/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/121592/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/121592/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/121592/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/121592/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/121592/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/121592/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/121592/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/121592/arquivos'}, {'id': 131347, 'receita_despesa': 'Despesa', 'numero': '2021NE000045', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.002717/2021-15', 'objeto': 'PRODUTOS ALIMENTÍCIOS - ADOÇANTES 45 FRASCOS', 'amparo_legal': 'LEI 10.520 / 2002 - Artigo: 1', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '113602', 'licitacao_numero': '00001/2021', 'sistema_origem_licitacao': None, 'data_assinatura': '2021-11-30', 'data_publicacao': '2021-11-30', 'data_proposta_comercial': None, 'vigencia_inicio': '2021-11-30', 'vigencia_fim': '2021-12-01', 'valor_inicial': '3.263,50', 'valor_global': '3.263,50', 'num_parcelas': 1, 'valor_parcela': '3.263,50', 'valor_acumulado': '3.263,50', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '22.243.972/0001-40', 'fornecedor_nome': 'AGO MOVEIS E SERVICOS LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/131347/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/131347/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/131347/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/131347/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/131347/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/131347/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/131347/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/131347/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/131347/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/131347/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/131347/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/131347/arquivos'}, {'id': 138385, 'receita_despesa': 'Despesa', 'numero': '2022NE000001', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.003150/2021-96', 'objeto': 'RENOVAÇÃO DE (01) ASSINATURA EM VERSÃO DIGITAL, DA REVISTA THE ECONOMIST, PELO PERÍODO DE 12 MESES, REVISTA QUE INFORMA E OFERECE ANÁLISE DE NOTÍCIAS INTERNACIONAIS, POLÍTICA MUNDIAL, NEGÓCIOS, FINANÇAS, CIÊNCIA E TECNOLOGIA, COM REPUTAÇÃO DE FONTE DE INFORMAÇÃO INTELIGENTE E CONFIÁVEL QUE É IMPORTANTE PARA SUBSIDIAR PESQUISAS REALIZADAS NO INSTITUTO.', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 25', 'informacao_complementar': None, 'codigo_modalidade': '07', 'modalidade': 'Inexigibilidade', 'unidade_compra': '113602', 'licitacao_numero': '00001/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2022-01-26', 'data_publicacao': '2022-01-26', 'data_proposta_comercial': None, 'vigencia_inicio': '2022-02-26', 'vigencia_fim': '2023-02-26', 'valor_inicial': '1.165,06', 'valor_global': '1.165,06', 'num_parcelas': 1, 'valor_parcela': '1.165,06', 'valor_acumulado': '1.165,06', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '07.492.558/0001-80', 'fornecedor_nome': 'MEDIA INTERNATIONAL DISTRIBUICAO DE PUBLICACOES LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/138385/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/138385/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/138385/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/138385/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/138385/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/138385/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/138385/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/138385/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/138385/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/138385/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/138385/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/138385/arquivos'}, {'id': 141853, 'receita_despesa': 'Despesa', 'numero': '00001/2022', 'codigo_tipo': '50', 'tipo': 'Contrato', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.002349/2021-05', 'objeto': 'CONTRATAÇÃO DE PRODUTOS E SERVIÇOS POR MEIO DE PACOTE DE SERVIÇOS DOS CORREIOS MEDIANTE ADESÃO AO TERMO DE CONDIÇÕES COMERCIAIS E ANEXOS, QUANDO CONTRATADOS SERVIÇOS ESPECÍFICOS, QUE PERMITE A COMPRA DE PRODUTOS E UTILIZAÇÃO DOS DIVERSOS SERVIÇOS DOS CORREIOS POR MEIO DOS CANAIS DE ATENDIMENTO DISPONIBILIZADOS.', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: III', 'informacao_complementar': 'SERVIÇOS POSTAIS NÃO COMPREENDIDOS NO MONOPÓLIO ESTATAL DOS CORREIOS (SEDEX/PAC)', 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00001/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2022-03-31', 'data_publicacao': '2023-05-30', 'data_proposta_comercial': None, 'vigencia_inicio': '2022-03-31', 'vigencia_fim': '2025-03-31', 'valor_inicial': '4.493,81', 'valor_global': '4.691,09', 'num_parcelas': 12, 'valor_parcela': '390,92', 'valor_acumulado': '13.678,56', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '34.028.316/0002-94', 'fornecedor_nome': 'EMPRESA BRASILEIRA DE CORREIOS E TELEGRAFOS', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/141853/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/141853/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/141853/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/141853/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/141853/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/141853/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/141853/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/141853/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/141853/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/141853/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/141853/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/141853/arquivos'}, {'id': 148548, 'receita_despesa': 'Despesa', 'numero': '2022NE000024', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.000950/2022-36', 'objeto': 'CONTRATAÇÃO DE SERVIÇO DE ACESSO A UM BANCO DE PREÇOS PÚBLICOS POR MEIO DE SENHA PESSOAL PARA USO DO SEACC- IPEA/RJ E DO SETOR DE COMPRAS DO IPEA/BSB', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00005/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2022-05-25', 'data_publicacao': '2022-05-26', 'data_proposta_comercial': None, 'vigencia_inicio': '2022-05-25', 'vigencia_fim': '2023-05-25', 'valor_inicial': '8.500,00', 'valor_global': '8.500,00', 'num_parcelas': 1, 'valor_parcela': '8.500,00', 'valor_acumulado': '8.500,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '16.538.909/0001-38', 'fornecedor_nome': 'PROMAXIMA GESTAO EMPRESARIAL LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/148548/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/148548/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/148548/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/148548/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/148548/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/148548/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/148548/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/148548/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/148548/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/148548/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/148548/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/148548/arquivos'}, {'id': 156286, 'receita_despesa': 'Despesa', 'numero': '2022NE000019', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.000486/2022-88', 'objeto': 'PRESTAÇÃO DE SERVIÇOS DE ENGENHARIA PARA LIMPEZA ROBOTIZADA, POR ESCOVAÇÃO A SECO COM FILMAGEM SIMULTÂNEA, DE REDE DE DUTOS E DESCONTAMINAÇÃO DE SISTEMA DEAR CONDICIONADO E VENTILAÇÃO DAS INSTALAÇÕES DO CPD DO IPEA/RJ, \\r\\n LOCALIZADO NA AV. PRESIDENTE VARGAS, Nº730/16° ANDAR NO CENTRO DO RIO DE JANEIRO. OS SERVIÇOS COMPREENDEM LIMPEZA E DESCONTAMINAÇÃO 01(UMA) VEZ AO ANO E EMISSÃO DE LAUDOS COM AVALIAÇÃO DA QUALIDADE DO AR 02 (DUAS) VEZES AO\\r\\nANO, A CADA 06 (SEIS) MESES.', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: I', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00004/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2022-04-08', 'data_publicacao': '2022-04-09', 'data_proposta_comercial': None, 'vigencia_inicio': '2022-04-08', 'vigencia_fim': '2023-01-30', 'valor_inicial': '1.490,00', 'valor_global': '1.490,00', 'num_parcelas': 1, 'valor_parcela': '1.490,00', 'valor_acumulado': '1.490,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '31.316.334/0001-00', 'fornecedor_nome': 'REFRIMEC REFRIGERACAO LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/156286/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/156286/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/156286/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/156286/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/156286/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/156286/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/156286/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/156286/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/156286/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/156286/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/156286/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/156286/arquivos'}, {'id': 166569, 'receita_despesa': 'Despesa', 'numero': '20220/0003', 'codigo_tipo': '98', 'tipo': 'Outros', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.002692/2022-22', 'objeto': 'PARAFUSADEIRA/FURADEIRA À BATERIA 20V BIVOLT 1/2\" 1500RPM PORTÁTIL - VELOCIDAD E VARIÁVEL E REVERSÍVEL - MANDRIL DE APERTO RÁPIDO - UM CARREGADOR E DUAS BAT ERIAS - ITEM DE REFERÊNCIA: DEWALT DCD776C2- BR', 'amparo_legal': 'LEI 10.520 / 2002 - Artigo: 1', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '113602', 'licitacao_numero': '00077/2021', 'sistema_origem_licitacao': None, 'data_assinatura': '2022-10-18', 'data_publicacao': '2022-10-19', 'data_proposta_comercial': None, 'vigencia_inicio': '2022-10-18', 'vigencia_fim': '2022-11-07', 'valor_inicial': '940,00', 'valor_global': '940,00', 'num_parcelas': 1, 'valor_parcela': '940,00', 'valor_acumulado': '940,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '39.877.684/0001-40', 'fornecedor_nome': 'COMERCIAL AVAN LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/166569/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/166569/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/166569/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/166569/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/166569/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/166569/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/166569/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/166569/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/166569/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/166569/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/166569/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/166569/arquivos'}, {'id': 170246, 'receita_despesa': 'Despesa', 'numero': '00000/0000', 'codigo_tipo': '98', 'tipo': 'Outros', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.002692/2022-22', 'objeto': '333903016 - MATERIAL DE EXPEDIENTE', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '113602', 'licitacao_numero': '00009/2021', 'sistema_origem_licitacao': None, 'data_assinatura': '2022-10-18', 'data_publicacao': '2022-10-20', 'data_proposta_comercial': None, 'vigencia_inicio': '2022-10-18', 'vigencia_fim': '2022-12-31', 'valor_inicial': '1.390,60', 'valor_global': '1.390,60', 'num_parcelas': 1, 'valor_parcela': '1.390,60', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '26.854.929/0001-71', 'fornecedor_nome': 'DIDAQUE EMPREENDIMENTOS LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/170246/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/170246/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/170246/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/170246/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/170246/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/170246/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/170246/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/170246/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/170246/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/170246/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/170246/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/170246/arquivos'}, {'id': 175309, 'receita_despesa': 'Despesa', 'numero': '2022NE000048', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SELOPE E SEINF', 'processo': '03001.002707/2022-52', 'objeto': 'AQUISIÇÃO PARA ATENDER A DEMANDA POR MATERIAL DE CONSUMO DE APOIO AS ATIVIDADES ROTINEIRAS DA GERIO DO IPEA-INSTITUTO DE PESQUISA ECONOMICA APLICADA NO RIO DE JANEIRO, CONFORME CONDIÇÕES, QUANTIDADES E EXIGÊNCIAS ESTABELECIDAS NO TERMO DE REFERÊNCIA (SEI 0485055)- ITENS 14 E 15.', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: I', 'informacao_complementar': 'VISA DAR SUPORTE PARA MELHORIA NAS AÇÕES E MANUTENÇÃO DO ATENDIMENTO DAS ATIVIDADES DESEMPENHADAS PELAS ÁREAS DEMANDANTES SELOP, SEINF NO IPEA-RJ.', 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00016/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2022-12-06', 'data_publicacao': '2022-12-27', 'data_proposta_comercial': None, 'vigencia_inicio': '2022-12-06', 'vigencia_fim': '2022-12-31', 'valor_inicial': '527,30', 'valor_global': '527,30', 'num_parcelas': 1, 'valor_parcela': '527,30', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '38.485.259/0001-42', 'fornecedor_nome': 'MAKTUB DISTRIBUIDORA LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/175309/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/175309/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/175309/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/175309/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/175309/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/175309/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/175309/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/175309/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/175309/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/175309/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/175309/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/175309/arquivos'}, {'id': 175628, 'receita_despesa': 'Despesa', 'numero': '2022NE000044', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.002707/2022-52', 'objeto': 'AQUISIÇÃO DE 02 FORNOS DE MICRO-ONDAS E 01 REFRIGERADOR FRIGOBAR PARA SUPORTE E MELHORIA NAS AÇÕES E MANUTENÇÃO DO ATENDIMENTO DAS ATIVIDADES DESEMPENHADAS PELAS ÁREAS DEMANTES NO IPEA-RJ', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00011/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2022-11-22', 'data_publicacao': '2022-12-29', 'data_proposta_comercial': None, 'vigencia_inicio': '2022-11-22', 'vigencia_fim': '2022-12-31', 'valor_inicial': '3.915,80', 'valor_global': '3.915,80', 'num_parcelas': 1, 'valor_parcela': '3.915,80', 'valor_acumulado': '3.915,80', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '37.502.052/0001-76', 'fornecedor_nome': 'OBEN COMERCIAL LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/175628/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/175628/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/175628/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/175628/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/175628/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/175628/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/175628/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/175628/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/175628/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/175628/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/175628/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/175628/arquivos'}, {'id': 180914, 'receita_despesa': 'Despesa', 'numero': '2022NE000045', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.002707/2022-52', 'objeto': 'AQUISIÇÃO DE 03 (TRÊS) CAFETEIRAS INDUSTRIAIS PARA ATENDIMENTO AO IPEA/RJ.', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00012/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2022-11-22', 'data_publicacao': '2023-02-01', 'data_proposta_comercial': '2022-09-22', 'vigencia_inicio': '2022-11-22', 'vigencia_fim': '2023-01-31', 'valor_inicial': '6.210,00', 'valor_global': '6.210,00', 'num_parcelas': 1, 'valor_parcela': '6.210,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '11.094.173/0001-32', 'fornecedor_nome': 'OFFICE DO BRASIL IMPORTACAO E EXPORTACAO LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/180914/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/180914/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/180914/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/180914/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/180914/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/180914/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/180914/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/180914/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/180914/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/180914/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/180914/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/180914/arquivos'}, {'id': 181487, 'receita_despesa': 'Despesa', 'numero': '2022NE000046', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.002912/2022-18', 'objeto': 'SERVIÇO DE CONFECÇÃO DE CORDÕES PARA CRACHÁS A SEREM UTILIZADOS NAS DEPENDÊNCIAS DO IPEA NO ED. BACEN.', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00014/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2022-11-21', 'data_publicacao': '2023-02-07', 'data_proposta_comercial': None, 'vigencia_inicio': '2022-11-21', 'vigencia_fim': '2023-02-15', 'valor_inicial': '4.120,00', 'valor_global': '4.120,00', 'num_parcelas': 1, 'valor_parcela': '4.120,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '38.458.393/0001-54', 'fornecedor_nome': 'EXXPRESS BRINDES LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/181487/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/181487/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/181487/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/181487/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/181487/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/181487/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/181487/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/181487/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/181487/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/181487/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/181487/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/181487/arquivos'}, {'id': 183180, 'receita_despesa': 'Despesa', 'numero': '2023NE000015', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'Biblioteca', 'processo': '03001.003730/2022-64', 'objeto': 'RENOVAÇÃO DE 01 ASSINATURA DIGITAL DA REVISTA THE ECONOMIST PELO PERÍODO DE 12 MESES.', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 74 - Inciso: CAPUT', 'informacao_complementar': None, 'codigo_modalidade': '07', 'modalidade': 'Inexigibilidade', 'unidade_compra': '113602', 'licitacao_numero': '00002/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-02-15', 'data_publicacao': '2023-02-24', 'data_proposta_comercial': None, 'vigencia_inicio': '2023-02-27', 'vigencia_fim': '2023-12-31', 'valor_inicial': '1.074,00', 'valor_global': '1.074,00', 'num_parcelas': 1, 'valor_parcela': '1.074,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '07.492.558/0001-80', 'fornecedor_nome': 'MEDIA INTERNATIONAL DISTRIBUICAO DE PUBLICACOES LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/183180/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/183180/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/183180/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/183180/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/183180/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/183180/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/183180/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/183180/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/183180/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/183180/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/183180/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/183180/arquivos'}, {'id': 183517, 'receita_despesa': 'Despesa', 'numero': '00002/2023', 'codigo_tipo': '50', 'tipo': 'Contrato', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.003357/2022-41', 'objeto': 'CONTRATAÇÃO DO LICENCIAMENTO DE CONTEÚDOS NOTICIOSOS BROADCAST, INCLUINDO OS PACOTES DO BROADCAST NEWS + O PACOTE ADD ON AGRO.', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 74 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '07', 'modalidade': 'Inexigibilidade', 'unidade_compra': '113602', 'licitacao_numero': '00001/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-02-27', 'data_publicacao': '2023-03-03', 'data_proposta_comercial': '2023-01-12', 'vigencia_inicio': '2023-03-02', 'vigencia_fim': '2025-03-02', 'valor_inicial': '44.217,30', 'valor_global': '46.211,56', 'num_parcelas': 24, 'valor_parcela': '1.925,48', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '62.652.961/0001-38', 'fornecedor_nome': 'AGENCIA ESTADO S.A', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/183517/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/183517/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/183517/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/183517/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/183517/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/183517/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/183517/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/183517/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/183517/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/183517/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/183517/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/183517/arquivos'}, {'id': 183520, 'receita_despesa': 'Despesa', 'numero': '00001/2023', 'codigo_tipo': '50', 'tipo': 'Contrato', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'SEINF', 'processo': '03001.003954/2022-76', 'objeto': 'CONTRATAÇÃO CONJUNTA DE PRESTAÇÃO DE SERVIÇO MÓVEL PESSOAL (SMP - DADOS MÓVEIS E VOZ), GESTÃO DE DISPOSITIVOS MÓVEIS (MDM) E OPÇÃO POR APARELHOS MÓVEIS EM COMODATO, CONFORME ESPECIFICAÇÕES E CONDIÇÕES CONSTANTES NO TERMO DE REFERÊNCIA.', 'amparo_legal': 'LEI 10.520 / 2002 - Artigo: 1', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '201057', 'licitacao_numero': '00013/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-03-01', 'data_publicacao': '2023-03-02', 'data_proposta_comercial': '2022-11-17', 'vigencia_inicio': '2023-03-01', 'vigencia_fim': '2025-09-01', 'valor_inicial': '9.100,80', 'valor_global': '9.340,20', 'num_parcelas': 1, 'valor_parcela': '9.340,20', 'valor_acumulado': '149.443,20', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '40.432.544/0001-47', 'fornecedor_nome': 'CLARO S.A.', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/183520/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/183520/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/183520/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/183520/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/183520/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/183520/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/183520/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/183520/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/183520/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/183520/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/183520/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/183520/arquivos'}, {'id': 183611, 'receita_despesa': 'Despesa', 'numero': '2022NE000049', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SELOPE E SEINF', 'processo': '03001.002707/2022-52', 'objeto': 'AQUISIÇÃO DE MATERIAL DE CONSUMO PARA ATENDER A DEMANDA DAS ATIVIDADES ROTINEIRAS E DE APOIO DA GERIO DO IPEA-RJ', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00015/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2022-12-06', 'data_publicacao': '2023-03-01', 'data_proposta_comercial': '2022-11-09', 'vigencia_inicio': '2022-12-08', 'vigencia_fim': '2023-02-28', 'valor_inicial': '1.264,22', 'valor_global': '1.264,22', 'num_parcelas': 1, 'valor_parcela': '1.264,22', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '46.814.710/0001-56', 'fornecedor_nome': 'LIDIANE DE SOUZA SAIOL PONTES 09944529770', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/183611/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/183611/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/183611/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/183611/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/183611/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/183611/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/183611/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/183611/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/183611/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/183611/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/183611/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/183611/arquivos'}, {'id': 184872, 'receita_despesa': 'Despesa', 'numero': '2022NE000043', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.002707/2022-52', 'objeto': 'AQUISIÇÃO DE APONTADOR DE LÁPIS ELÉTRICO', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00010/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2022-11-21', 'data_publicacao': '2023-03-13', 'data_proposta_comercial': None, 'vigencia_inicio': '2022-11-21', 'vigencia_fim': '2023-03-31', 'valor_inicial': '106,50', 'valor_global': '106,50', 'num_parcelas': 1, 'valor_parcela': '106,50', 'valor_acumulado': '106,50', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '46.814.710/0001-56', 'fornecedor_nome': 'LIDIANE DE SOUZA SAIOL PONTES 09944529770', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/184872/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/184872/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/184872/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/184872/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/184872/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/184872/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/184872/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/184872/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/184872/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/184872/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/184872/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/184872/arquivos'}, {'id': 187512, 'receita_despesa': 'Despesa', 'numero': '2023NE000032', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.000001/2023-37', 'objeto': 'AQUISIÇÃO DE 06 SPLITS DE AR CONDICIONADO DE 12.000 BTUS, VISANDO PROPICIAR, DURANTE A NOITE, FINS DE SEMANA E FERIADOS, MELHOR CLIMATIZAÇÃO DAS SALAS DOS RACKS QUE ACONDICIONAM OS SWITCHES DE REDE, LOCALIZADOS NO 17º ANDAR - (SALA 1720), 18º ANDAR - (SALA 1819) E 19º ANDAR - (SALA 1911) DO IPEA/RJ', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00002/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-03-27', 'data_publicacao': '2023-03-31', 'data_proposta_comercial': '2023-03-20', 'vigencia_inicio': '2023-03-27', 'vigencia_fim': '2023-05-24', 'valor_inicial': '21.000,00', 'valor_global': '21.000,00', 'num_parcelas': 1, 'valor_parcela': '21.000,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '30.773.398/0001-60', 'fornecedor_nome': 'KAY COMERCIO E PRESTACAO DE SERVICOS LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/187512/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/187512/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/187512/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/187512/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/187512/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/187512/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/187512/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/187512/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/187512/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/187512/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/187512/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/187512/arquivos'}, {'id': 187522, 'receita_despesa': 'Despesa', 'numero': '2023NE000033', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'SEINF', 'processo': '03001.000001/2023-37', 'objeto': 'SERVIÇO DE INSTALAÇÃO DE 06 SPLITS DE AR CONDICIONADO DE 12.000 BTUS, VISANDO PROPICIAR, DURANTE A NOITE, FINS DE SEMANA E FERIADOS, MELHOR CLIMATIZAÇÃO DAS SALAS DOS RACKS QUE ACONDICIONAM OS SWITCHES DE REDE, LOCALIZADOS NO 17º ANDAR - (SALA 1720), 18º ANDAR - (SALA 1819) E 19º ANDAR - (SALA 1911) DO IPEA/RJ', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00002/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-03-27', 'data_publicacao': '2023-03-31', 'data_proposta_comercial': None, 'vigencia_inicio': '2023-03-27', 'vigencia_fim': '2023-05-24', 'valor_inicial': '8.100,00', 'valor_global': '8.100,00', 'num_parcelas': 1, 'valor_parcela': '8.100,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '30.773.398/0001-60', 'fornecedor_nome': 'KAY COMERCIO E PRESTACAO DE SERVICOS LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/187522/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/187522/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/187522/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/187522/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/187522/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/187522/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/187522/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/187522/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/187522/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/187522/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/187522/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/187522/arquivos'}, {'id': 188999, 'receita_despesa': 'Despesa', 'numero': '2023NE000034', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.001058/2023-53', 'objeto': 'CONTRATAÇÃO DE SERVIÇO DE INTERNET -LINK DE INTERNET REDUNDANTE PARA O IPEA - UNIDADE RIO DE JANEIRO', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: I', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00003/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-04-04', 'data_publicacao': '2023-04-13', 'data_proposta_comercial': '2023-03-30', 'vigencia_inicio': '2023-04-04', 'vigencia_fim': '2024-04-04', 'valor_inicial': '14.149,56', 'valor_global': '14.149,56', 'num_parcelas': 1, 'valor_parcela': '14.149,56', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '72.843.212/0001-41', 'fornecedor_nome': 'CIRION TECHNOLOGIES DO BRASIL LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/188999/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/188999/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/188999/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/188999/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/188999/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/188999/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/188999/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/188999/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/188999/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/188999/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/188999/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/188999/arquivos'}, {'id': 192611, 'receita_despesa': 'Despesa', 'numero': '2023NE000035', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SEINF', 'processo': '03001.000715/2023-45', 'objeto': 'AQUISIÇÃO DE 20 (VINTE) UNIDADES DE ARMAZENAMENTO INTERNO (HARD DRIVES -- SSD)', 'amparo_legal': 'LEI 10.520 / 2002 - Artigo: 1', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '153032', 'licitacao_numero': '00071/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-04-11', 'data_publicacao': '2023-05-09', 'data_proposta_comercial': None, 'vigencia_inicio': '2023-04-11', 'vigencia_fim': '2023-05-24', 'valor_inicial': '3.400,00', 'valor_global': '3.400,00', 'num_parcelas': 1, 'valor_parcela': '3.400,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '34.238.351/0001-57', 'fornecedor_nome': 'P & F IMPORTACAO E EXPORTACAO LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/192611/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/192611/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/192611/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/192611/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/192611/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/192611/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/192611/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/192611/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/192611/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/192611/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/192611/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/192611/arquivos'}, {'id': 194180, 'receita_despesa': 'Despesa', 'numero': '2023NE000040', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.000540/2023-76', 'objeto': 'CONTRATAÇÃO DE EMPRESA ESPECIALIZADA NA EXECUÇÃO DE SERVIÇOS DE ENGENHARIA (NÃO CONTINUADOS) PARA LIMPEZA ROBOTIZADA, POR ESCOVAÇÃO A SECO COM FILMAGEM SIMULTÂNEA, DE REDE DE DUTOS E DESCONTAMINAÇÃO DE SISTEMA DE AR CONDICIONADO E VENTILAÇÃO, COMPREENDENDO OS SEGUINTES SERVIÇOS: LIMPEZA E DESCONTAMIZAÇÃO 01 (UMA) VEZ AO ANO; EMISSÃO DE LAUDOS COM AVALIAÇÃO DA QUALIDADE DO AR 02 (DUAS) VEZES AO ANO, A CADA 06 (SEIS) MESES.', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00006/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-05-17', 'data_publicacao': '2023-05-19', 'data_proposta_comercial': '2023-05-16', 'vigencia_inicio': '2023-05-17', 'vigencia_fim': '2023-12-31', 'valor_inicial': '2.600,00', 'valor_global': '2.600,00', 'num_parcelas': 3, 'valor_parcela': '866,67', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '02.819.827/0001-57', 'fornecedor_nome': 'O. A. M. COMERCIAL E SERVICOS LTDA.', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/194180/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/194180/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/194180/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/194180/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/194180/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/194180/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/194180/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/194180/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/194180/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/194180/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/194180/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/194180/arquivos'}, {'id': 195184, 'receita_despesa': 'Despesa', 'numero': '2023NE000041', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.000543/2023-18', 'objeto': 'AQUISIÇÃO DE 270 KG DECAFÉ EM PÓ, TORRADO E MOÍDO, EMBALADO À VÁCUO (TORRA MÉDIA ) (18 MESES NO MÍNIMO DE VALIDADE) CONFORME CONDIÇÕES E EXIGÊNCIAS ESTABELECIDAS NO TERMO DE REFERÊNCIA', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00005/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-05-18', 'data_publicacao': '2023-05-25', 'data_proposta_comercial': '2023-05-10', 'vigencia_inicio': '2023-05-18', 'vigencia_fim': '2023-06-23', 'valor_inicial': '7.246,80', 'valor_global': '7.246,80', 'num_parcelas': 1, 'valor_parcela': '7.246,80', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '27.245.852/0001-03', 'fornecedor_nome': 'SUL BRASIL ATACADISTA LIMITADA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/195184/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/195184/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/195184/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/195184/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/195184/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/195184/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/195184/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/195184/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/195184/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/195184/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/195184/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/195184/arquivos'}, {'id': 195195, 'receita_despesa': 'Despesa', 'numero': '2023NE000042', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.000543/2023-18', 'objeto': 'AQUISIÇÃO DE 135 KG DE AÇÚCAR REFINADO, BRANCO, EMBALAGEM DE 1KG (12 MESES NO MÍNIMO DE VALIDADE), E DE 108 FRASCOS DE ADOÇANTE LÍQUIDO SUCRALOSE - FRASCO DE 100ML (18 MESES NO MÍNIMO DE VALIDADE) CONFORME CONDIÇÕES E EXIGÊNCIAS ESTABELECIDAS NO TERMO DE REFERÊNCIA', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00005/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-05-18', 'data_publicacao': '2023-05-25', 'data_proposta_comercial': '2023-05-15', 'vigencia_inicio': '2023-05-18', 'vigencia_fim': '2023-06-23', 'valor_inicial': '1.852,20', 'valor_global': '1.852,20', 'num_parcelas': 1, 'valor_parcela': '1.852,20', 'valor_acumulado': '1.852,20', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '39.643.017/0001-00', 'fornecedor_nome': 'NGV DISTRIBUIDORA E SOLUCOES LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/195195/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/195195/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/195195/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/195195/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/195195/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/195195/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/195195/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/195195/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/195195/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/195195/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/195195/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/195195/arquivos'}, {'id': 197072, 'receita_despesa': 'Despesa', 'numero': '2023NE000045', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.000543/2023-18', 'objeto': 'AQUISIÇÃO DE 09 KG DE CAFÉ TORRADO EM GRÃOS, CONFORME CONDIÇÕES E EXIGÊNCIAS ESTABELECIDAS NO TERMO DE REFERÊNCIA –SEI (0531607).', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00007/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-05-31', 'data_publicacao': '2023-06-06', 'data_proposta_comercial': '2023-05-22', 'vigencia_inicio': '2023-05-31', 'vigencia_fim': '2023-06-23', 'valor_inicial': '702,00', 'valor_global': '702,00', 'num_parcelas': 1, 'valor_parcela': '702,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '31.021.788/0001-46', 'fornecedor_nome': 'F PEREIRA COMERCIO E DISTRIBUIDORA LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/197072/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/197072/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/197072/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/197072/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/197072/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/197072/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/197072/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/197072/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/197072/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/197072/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/197072/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/197072/arquivos'}, {'id': 200160, 'receita_despesa': 'Despesa', 'numero': '2023NE000039', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'COACC', 'processo': '03001.001470/2023-73', 'objeto': 'CAPACITAÇÃO DE SERVIDOR NO \"10º CONTRATOS WEEK - SEMANA NACIONAL DE ESTUDOS AVANÇADOS EM CONTRATOS ADMINISTRATIVOS\" REALIZADO EM FOZ DO IGUAÇU - PARANÁ, NO PERÍODO DE 12 A 16/06/2023', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 25', 'informacao_complementar': None, 'codigo_modalidade': '07', 'modalidade': 'Inexigibilidade', 'unidade_compra': '113602', 'licitacao_numero': '00003/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-05-16', 'data_publicacao': '2023-06-28', 'data_proposta_comercial': None, 'vigencia_inicio': '2023-05-16', 'vigencia_fim': '2023-07-04', 'valor_inicial': '5.399,00', 'valor_global': '5.399,00', 'num_parcelas': 1, 'valor_parcela': '5.399,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '10.498.974/0002-81', 'fornecedor_nome': 'INSTITUTO NEGOCIOS PUBLICOS DO BRASIL - ESTUDOS E PESQUISAS NA ADMNIISTRACAO PUB', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/200160/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/200160/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/200160/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/200160/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/200160/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/200160/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/200160/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/200160/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/200160/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/200160/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/200160/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/200160/arquivos'}, {'id': 201254, 'receita_despesa': 'Despesa', 'numero': '2023NE000038', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.000697/2023-00', 'objeto': 'SERVIÇO DE RECARGA NOS EXTINTORES PERTENCNETES AO IPEA/RJ', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00004/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-05-02', 'data_publicacao': '2023-07-05', 'data_proposta_comercial': '2023-04-25', 'vigencia_inicio': '2023-05-02', 'vigencia_fim': '2023-07-10', 'valor_inicial': '1.000,00', 'valor_global': '1.000,00', 'num_parcelas': 1, 'valor_parcela': '1.000,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '22.159.233/0001-74', 'fornecedor_nome': 'T. V. DA SILVA SERVICOS E EQUIPAMENTOS CONTRA INCENDIO', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/201254/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/201254/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/201254/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/201254/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/201254/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/201254/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/201254/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/201254/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/201254/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/201254/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/201254/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/201254/arquivos'}, {'id': 214627, 'receita_despesa': 'Despesa', 'numero': '2023NE000049', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.002537/2023-97', 'objeto': 'AQUISIÇÃO DE 01 UNIDADE DE REFRIERADOR DUPLEX', 'amparo_legal': 'LEI 10.520 / 2002 - Artigo: 1', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '160228', 'licitacao_numero': '00004/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-09-08', 'data_publicacao': '2023-09-22', 'data_proposta_comercial': '2023-01-16', 'vigencia_inicio': '2023-09-08', 'vigencia_fim': '2023-12-31', 'valor_inicial': '2.627,00', 'valor_global': '2.627,00', 'num_parcelas': 1, 'valor_parcela': '2.627,00', 'valor_acumulado': '2.627,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '28.315.329/0001-60', 'fornecedor_nome': 'LL COMERCIO DE EQUIPAMENTOS LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/214627/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/214627/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/214627/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/214627/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/214627/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/214627/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/214627/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/214627/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/214627/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/214627/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/214627/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/214627/arquivos'}, {'id': 214630, 'receita_despesa': 'Despesa', 'numero': '2023NE000052', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'SEEOF', 'processo': '03001.002845/2023-12', 'objeto': 'CAPACITAÇÃO DOS SERVIDORES MARIA HOSANA CARNEIRO DA CUNHA E RAUL JOSÉ CORDEIRO LEMOS - \"CURSO SIAFI – TEÓRICO E PRÁTICO DE EXECUÇÃO ORÇAMENTÁRIA E FINANCEIRA NO SISTEMA\"', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 25', 'informacao_complementar': None, 'codigo_modalidade': '07', 'modalidade': 'Inexigibilidade', 'unidade_compra': '113602', 'licitacao_numero': '00005/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-09-15', 'data_publicacao': '2023-09-22', 'data_proposta_comercial': '2023-09-05', 'vigencia_inicio': '2023-09-15', 'vigencia_fim': '2023-11-22', 'valor_inicial': '3.180,00', 'valor_global': '3.180,00', 'num_parcelas': 1, 'valor_parcela': '3.180,00', 'valor_acumulado': '3.180,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '14.087.594/0001-24', 'fornecedor_nome': 'MMP CURSOS CAPACITACAO E TREINAMENTO LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/214630/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/214630/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/214630/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/214630/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/214630/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/214630/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/214630/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/214630/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/214630/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/214630/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/214630/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/214630/arquivos'}, {'id': 214631, 'receita_despesa': 'Despesa', 'numero': '2023NE000053', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.001989/2023-51', 'objeto': 'AQUISIÇÃO DE 350 PACOTES COM 1000 FOLHAS - PAPEL TOALHA, COMPRIMENTO 20 CM, LARGURA 21 CM, COR BRANCA, INTERFOLHADA, 2 DOBRAS', 'amparo_legal': 'LEI 10.520 / 2002 - Artigo: 1', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '160242', 'licitacao_numero': '00058/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-09-19', 'data_publicacao': '2023-09-22', 'data_proposta_comercial': '2022-11-14', 'vigencia_inicio': '2023-09-19', 'vigencia_fim': '2023-10-31', 'valor_inicial': '3.325,00', 'valor_global': '3.325,00', 'num_parcelas': 1, 'valor_parcela': '3.325,00', 'valor_acumulado': '3.325,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '22.965.625/0001-20', 'fornecedor_nome': 'LABUTAR DISTRIBUIDORA E PRESTADORA DE SERVICO LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/214631/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/214631/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/214631/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/214631/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/214631/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/214631/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/214631/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/214631/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/214631/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/214631/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/214631/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/214631/arquivos'}, {'id': 214632, 'receita_despesa': 'Despesa', 'numero': '2023NE000054', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.002537/2023-97', 'objeto': 'AQUISIÇÃO DE 2 UNIDADES TELEVISOR, SMART TV, 4K, WIFI, ENTRADAS HDMI/USB, TELA 65 POLEGADAS, TELA TIPO LED, VOLTAGEM 110/220V, CONTROLE REMOTO.', 'amparo_legal': 'LEI 10.520 / 2002 - Artigo: 1', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '153254', 'licitacao_numero': '00015/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-09-20', 'data_publicacao': '2023-09-22', 'data_proposta_comercial': '2022-11-25', 'vigencia_inicio': '2023-09-20', 'vigencia_fim': '2023-12-31', 'valor_inicial': '6.900,00', 'valor_global': '6.900,00', 'num_parcelas': 1, 'valor_parcela': '6.900,00', 'valor_acumulado': '6.900,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '65.149.197/0002-51', 'fornecedor_nome': 'REPREMIG REPRESENTACAO E COMERCIO DE MINAS GERAIS LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/214632/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/214632/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/214632/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/214632/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/214632/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/214632/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/214632/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/214632/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/214632/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/214632/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/214632/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/214632/arquivos'}, {'id': 218514, 'receita_despesa': 'Despesa', 'numero': '2023NE000056', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Informática (TIC)', 'subcategoria': None, 'unidades_requisitantes': 'Biblioteca', 'processo': '03001.001516/2023-54', 'objeto': 'AQUISIÇÃO DO SOFTWARE DE OCR, EM LÍNGUA PORTUGUESA, COM 10 LICENÇAS PERPÉTUAS, PARA USO DAS BIBLIOTECAS DO IPEA DO RIO DE JANEIRO E BRASÍLIA, SEUS USUÁRIOS, COMO DE USO PARA PESQUISA ACADÊMICA', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00008/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-09-21', 'data_publicacao': '2023-09-29', 'data_proposta_comercial': '2023-09-08', 'vigencia_inicio': '2023-09-21', 'vigencia_fim': '2023-10-30', 'valor_inicial': '11.010,70', 'valor_global': '11.010,70', 'num_parcelas': 1, 'valor_parcela': '11.010,70', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '12.007.998/0001-35', 'fornecedor_nome': 'PISONTEC COMERCIO E SERVICOS EM TECNOLOGIA DA INFORMACAO LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/218514/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/218514/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/218514/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/218514/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/218514/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/218514/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/218514/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/218514/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/218514/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/218514/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/218514/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/218514/arquivos'}, {'id': 218520, 'receita_despesa': 'Despesa', 'numero': '2023NE000055', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Informática (TIC)', 'subcategoria': None, 'unidades_requisitantes': 'Biblioteca', 'processo': '03001.001531/2023-01', 'objeto': 'AQUISIÇÃO DE 02 (DOIS) SCANNERS PLANETÁRIOS, PARA USO DAS BIBLIOTECAS DO IPEA DO RIO DE JANEIRO E BRASÍLIA, SEUS USUÁRIOS, COMO DE USO PARA PESQUISA ACADÊMICA. A AQUISIÇÃO DESSES SCANNERS TEM A FINALIDADE DE VIABILIZAR A DIGITALIZAÇÃO DO ACERVO DA MEMÓRIA BIBLIOGRÁFICA DO IPEA, QUE ATUALMENTE É PRESERVADO EM FORMATO FÍSICO PELA BIBLIOTECA DO IPEA, EM SUAS UNIDADES EM BRASÍLIA E NO RIO DE JANEIRO.', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 25', 'informacao_complementar': None, 'codigo_modalidade': '07', 'modalidade': 'Inexigibilidade', 'unidade_compra': '113602', 'licitacao_numero': '00006/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-09-21', 'data_publicacao': '2023-09-29', 'data_proposta_comercial': '2023-08-28', 'vigencia_inicio': '2023-09-21', 'vigencia_fim': '2023-11-08', 'valor_inicial': '9.800,00', 'valor_global': '9.800,00', 'num_parcelas': 1, 'valor_parcela': '9.800,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '01.464.579/0001-06', 'fornecedor_nome': 'SCANSYSTEM LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/218520/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/218520/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/218520/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/218520/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/218520/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/218520/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/218520/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/218520/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/218520/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/218520/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/218520/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/218520/arquivos'}, {'id': 225188, 'receita_despesa': 'Despesa', 'numero': '2023NE000063', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.002119/2023-08', 'objeto': 'AQUISIÇÃO DE MATERIAL DE CONSUMO PARA ATENDIMENTO DAS DEMANDAS APRESENTADAS PELO SELOP/ALMOXARIFADO NECESSÁRIAS PARA COMPOR ESTOQUE DE ALMOXARIFADO - ITEM 1 (CARTÃO DE VISITA) E ATENDIMENTO DO SERVIÇO DE COPEIRAGEM - ITEM 4 (GARRAFA TÉRMICA MATERIAL DE AÇO INOXIDÁVEL)', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00009/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-11-03', 'data_publicacao': '2023-11-07', 'data_proposta_comercial': '2023-09-11', 'vigencia_inicio': '2023-11-03', 'vigencia_fim': '2023-11-30', 'valor_inicial': '2.208,00', 'valor_global': '2.208,00', 'num_parcelas': 1, 'valor_parcela': '2.208,00', 'valor_acumulado': '2.208,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '21.666.913/0001-11', 'fornecedor_nome': 'COMERCIAL LB MIX LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/225188/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/225188/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/225188/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/225188/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/225188/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/225188/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/225188/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/225188/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/225188/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/225188/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/225188/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/225188/arquivos'}, {'id': 225193, 'receita_despesa': 'Despesa', 'numero': '2023NE000062', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.002119/2023-08', 'objeto': 'AQUISIÇÃO DE MATERIAL DE CONSUMO PARA ATENDIMENTO DAS DEMANDAS APRESENTADAS PELO SELOP/ALMOXARIFADO NECESSÁRIAS PARA O ATENDIMENTO DO SERVIÇO DE COPEIRAGEM- ITENS 2 (BANDEJA DE AÇO MATERIAL: AÇO INOXIDÁVEL), 3 (LEITEIRA MATERIAL: ALUMÍNIO) E 5 (GARRAFA TÉRMICA MATERIAL: PLÁSTICO RESISTENTE)', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00010/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-11-03', 'data_publicacao': '2023-11-07', 'data_proposta_comercial': '2023-09-05', 'vigencia_inicio': '2023-11-03', 'vigencia_fim': '2023-12-19', 'valor_inicial': '2.093,90', 'valor_global': '2.093,90', 'num_parcelas': 1, 'valor_parcela': '2.093,90', 'valor_acumulado': '2.093,90', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '13.808.799/0001-99', 'fornecedor_nome': 'LPK UTILIDADES LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/225193/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/225193/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/225193/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/225193/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/225193/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/225193/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/225193/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/225193/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/225193/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/225193/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/225193/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/225193/arquivos'}, {'id': 225448, 'receita_despesa': 'Despesa', 'numero': '2023NE000061', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SEINF', 'processo': '03001.002539/2023-86', 'objeto': 'AQUISIÇÃO DE MATERIAL DE CONSUMO TIC- MEMORIAS RAM- \" ITEM 1- MEMÓRIA RAM 8GB – DIMM – DDR3 – 1600MHZ – EQUIVALENTE AO MODELO KINGSTON KVR16N11/8– QUANTIDADE: 22 UNIDADES”.', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00011/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-11-03', 'data_publicacao': '2023-11-07', 'data_proposta_comercial': '2023-09-19', 'vigencia_inicio': '2023-11-03', 'vigencia_fim': '2023-12-12', 'valor_inicial': '1.857,68', 'valor_global': '1.857,68', 'num_parcelas': 1, 'valor_parcela': '1.857,68', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '20.473.312/0001-20', 'fornecedor_nome': 'A C P DA SILVA QUINOY COMERCIO E SERVICOS', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/225448/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/225448/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/225448/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/225448/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/225448/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/225448/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/225448/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/225448/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/225448/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/225448/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/225448/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/225448/arquivos'}, {'id': 225987, 'receita_despesa': 'Despesa', 'numero': '2023NE000064', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Informática (TIC)', 'subcategoria': None, 'unidades_requisitantes': 'SEINF', 'processo': '03001.002539/2023-86', 'objeto': 'AQUISIÇÃO DE 50 UNIDADES MEMÓRIA RAM 16GB - ITEM 2', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00012/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-11-07', 'data_publicacao': '2023-11-10', 'data_proposta_comercial': None, 'vigencia_inicio': '2023-11-07', 'vigencia_fim': '2023-12-12', 'valor_inicial': '11.000,00', 'valor_global': '11.000,00', 'num_parcelas': 1, 'valor_parcela': '11.000,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '48.837.707/0001-83', 'fornecedor_nome': 'MDP COMERCIO E IMPORTACAO LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/225987/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/225987/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/225987/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/225987/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/225987/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/225987/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/225987/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/225987/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/225987/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/225987/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/225987/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/225987/arquivos'}, {'id': 233804, 'receita_despesa': 'Despesa', 'numero': '2023NE000065', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.002119/2023-08', 'objeto': 'AQUISIÇÃO DE MATERIAL PERMANENTE (03 CARRINHOS DE DISTRIBUIÇÃO PARA USO NO SERVIÇO DE COPEIRAGEM)', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00013/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-11-30', 'data_publicacao': '2023-12-13', 'data_proposta_comercial': '2023-11-21', 'vigencia_inicio': '2023-11-30', 'vigencia_fim': '2023-12-31', 'valor_inicial': '9.670,50', 'valor_global': '9.670,50', 'num_parcelas': 1, 'valor_parcela': '9.670,50', 'valor_acumulado': '9.670,50', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '48.807.338/0001-86', 'fornecedor_nome': '48.807.338 WILLIAM BONILHA DE ARAUJO', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/233804/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/233804/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/233804/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/233804/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/233804/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/233804/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/233804/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/233804/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/233804/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/233804/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/233804/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/233804/arquivos'}, {'id': 233808, 'receita_despesa': 'Despesa', 'numero': '2023NE000066', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SEINF', 'processo': '03001.001989/2023-51', 'objeto': 'AQUISIÇÃO DE MATERIAL PERMANENTE - ESCADA ARTICULADA DE ALUMÍNIO DE 4 METROS COM 20 DEDGRAUS PARA USO DO SETOR DE INFORMÁTICA', 'amparo_legal': 'LEI 10.520 / 2002 - Artigo: 1', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '194075', 'licitacao_numero': '00002/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-12-07', 'data_publicacao': '2023-12-13', 'data_proposta_comercial': '2023-05-12', 'vigencia_inicio': '2023-12-07', 'vigencia_fim': '2023-12-22', 'valor_inicial': '950,00', 'valor_global': '950,00', 'num_parcelas': 1, 'valor_parcela': '950,00', 'valor_acumulado': '950,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '38.292.219/0001-84', 'fornecedor_nome': 'RAFAEL RODRIGUES CHOAIRY RODART', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/233808/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/233808/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/233808/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/233808/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/233808/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/233808/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/233808/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/233808/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/233808/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/233808/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/233808/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/233808/arquivos'}, {'id': 251883, 'receita_despesa': 'Despesa', 'numero': '00001/2024', 'codigo_tipo': '50', 'tipo': 'Contrato', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'SEINF E SELOP', 'processo': '03001.002213/2023-59', 'objeto': 'CONTRATAÇÃO DE SOLUÇÃO DE TECNOLOGIA DA INFORMAÇÃO E COMUNICAÇÃO DE SERVIÇO DE OUTSOURCING DE IMPRESSÃO, POR MEIO DE DISPONIBILIZAÇÃO DE MÁQUINAS FUNCIONAIS NOVAS, DE PRIMEIRO USO, PARA PRESTAÇÃO DE SERVIÇOS DE REPROGRAFIA DE DOCUMENTOS, COMPREENDENDO REPRODUÇÃO, IMPRESSÃO, DIGITALIZAÇÃO, INSTALAÇÃO, TREINAMENTO E CONFIGURAÇÃO DE SOFTWARE DE GERENCIAMENTO E BILHETAGEM COM GARANTIA DE FUNCIONAMENTO DA SOLUÇÃO, COM A DEVIDA MANUTENÇÃO E FORNECIMENTO DE SUPRIMENTOS, EXCETO PAPEL, SEM PREVISÃO DE CONSUMO MÍNIMO, PELO PERÍODO DE 48 MESES', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 28 - Inciso: I', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '113602', 'licitacao_numero': '90015/2024', 'sistema_origem_licitacao': None, 'data_assinatura': '2024-02-20', 'data_publicacao': '2024-02-22', 'data_proposta_comercial': '2024-01-18', 'vigencia_inicio': '2024-04-02', 'vigencia_fim': '2028-04-02', 'valor_inicial': '270.862,44', 'valor_global': '270.862,44', 'num_parcelas': 48, 'valor_parcela': '5.642,97', 'valor_acumulado': '270.862,56', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '07.366.296/0001-08', 'fornecedor_nome': 'NEO TECNOLOGIA DA INFORMATICA LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/251883/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/251883/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/251883/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/251883/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/251883/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/251883/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/251883/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/251883/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/251883/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/251883/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/251883/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/251883/arquivos'}, {'id': 252332, 'receita_despesa': 'Despesa', 'numero': '00002/2024', 'codigo_tipo': '50', 'tipo': 'Contrato', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'SEINF', 'processo': '03001.002713/2023-91', 'objeto': 'PRESTAÇÃO DE SERVIÇO DE ACESSO À INTERNET, COM PRAZO INICIAL DE 24 (VINTE E QUATRO) MESES, PODENDO SER PRORROGADO NOS TERMOS DA LEI Nº 14.133 DE 01/04/2021, UTILIZANDO LINKS DE ACESSO COM CAPACIDADE DE PROVER COMUNICAÇÃO DE DADOS, VOZ E IMAGENS POR COMUTAÇÃO DE PACOTES IP (INTERNET PROTOCOL)', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '90027/2024', 'sistema_origem_licitacao': None, 'data_assinatura': '2024-02-21', 'data_publicacao': '2024-02-23', 'data_proposta_comercial': '2024-02-09', 'vigencia_inicio': '2024-04-05', 'vigencia_fim': '2026-04-05', 'valor_inicial': '38.832,00', 'valor_global': '38.832,00', 'num_parcelas': 24, 'valor_parcela': '1.618,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '08.804.362/0001-47', 'fornecedor_nome': 'FACHINELI COMUNICACAO LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/252332/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/252332/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/252332/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/252332/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/252332/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/252332/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/252332/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/252332/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/252332/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/252332/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/252332/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/252332/arquivos'}, {'id': 263191, 'receita_despesa': 'Despesa', 'numero': '2024NE000017', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': '', 'processo': '03001.003819/2023-10', 'objeto': '33903007 - GENEROS DE ALIMENTAÇÃO - CAFÉ.', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': '11360206900142024 - UASG Minuta: 113602', 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '90014/2024', 'sistema_origem_licitacao': None, 'data_assinatura': '2024-03-27', 'data_publicacao': '2024-03-27', 'data_proposta_comercial': None, 'vigencia_inicio': '2024-03-27', 'vigencia_fim': '2024-04-19', 'valor_inicial': '6.641,60', 'valor_global': '6.641,60', 'num_parcelas': 1, 'valor_parcela': '6.641,60', 'valor_acumulado': '6.641,60', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '53.640.283/0001-00', 'fornecedor_nome': 'HIPER COMERCIO & SERVICOS LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/263191/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/263191/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/263191/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/263191/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/263191/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/263191/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/263191/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/263191/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/263191/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/263191/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/263191/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/263191/arquivos'}, {'id': 264096, 'receita_despesa': 'Despesa', 'numero': '2024NE000016', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.003819/2023-10', 'objeto': '98 FRASCOS DE ADOÇANTE', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '90014/2024', 'sistema_origem_licitacao': None, 'data_assinatura': '2024-03-19', 'data_publicacao': '2024-04-25', 'data_proposta_comercial': '2024-03-15', 'vigencia_inicio': '2024-03-19', 'vigencia_fim': '2024-04-19', 'valor_inicial': '988,82', 'valor_global': '988,82', 'num_parcelas': 1, 'valor_parcela': '988,82', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '27.494.420/0001-28', 'fornecedor_nome': 'SOARES COMERCIO E LICITACOES LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/264096/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/264096/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/264096/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/264096/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/264096/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/264096/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/264096/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/264096/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/264096/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/264096/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/264096/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/264096/arquivos'}, {'id': 264097, 'receita_despesa': 'Despesa', 'numero': '2024NE000015', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.003819/2023-10', 'objeto': '161 KG DE AÇÚCAR REFINADO, BRANCO', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '90014/2024', 'sistema_origem_licitacao': None, 'data_assinatura': '2024-03-19', 'data_publicacao': '2024-04-25', 'data_proposta_comercial': '2024-03-14', 'vigencia_inicio': '2024-03-19', 'vigencia_fim': '2024-04-19', 'valor_inicial': '818,85', 'valor_global': '818,85', 'num_parcelas': 1, 'valor_parcela': '818,85', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '52.120.603/0001-20', 'fornecedor_nome': '52.120.603 SILVANIA ABECASSIS DE CARVALHO', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/264097/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/264097/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/264097/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/264097/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/264097/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/264097/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/264097/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/264097/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/264097/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/264097/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/264097/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/264097/arquivos'}, {'id': 264441, 'receita_despesa': 'Despesa', 'numero': '2024NE000018', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços de Engenharia', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.000744/2024-98', 'objeto': 'LIMPEZA ROBOTIZADA, POR ESCOVAÇÃO A SECO COM FILMAGEM SIMULTÂNEA, DE REDE DE DUTOS E DESCONTAMINAÇÃO DE SISTEMA DE AR CONDICIONADO E VENTILAÇÃO DA SALA DO CPD DO IPEA/RJ', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: I', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '90023/2024', 'sistema_origem_licitacao': None, 'data_assinatura': '2024-04-19', 'data_publicacao': '2024-04-26', 'data_proposta_comercial': '2024-04-09', 'vigencia_inicio': '2024-04-19', 'vigencia_fim': '2024-12-31', 'valor_inicial': '2.962,50', 'valor_global': '2.962,50', 'num_parcelas': 1, 'valor_parcela': '2.962,50', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '36.724.325/0001-64', 'fornecedor_nome': 'GERIR COMERCIO E SERVICOS DE REFRIGERACAO LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/264441/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/264441/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/264441/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/264441/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/264441/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/264441/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/264441/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/264441/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/264441/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/264441/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/264441/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/264441/arquivos'}, {'id': 295851, 'receita_despesa': 'Despesa', 'numero': '00003/2024', 'codigo_tipo': '50', 'tipo': 'Contrato', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.003759/2023-27', 'objeto': 'SERVIÇOS CONTÍNUOS DE COPEIRAGEM, INCLUINDO O FORNECIMENTO DE MATERIAIS DE GÊNERO ALIMENTÍCIO (CAFÉ EM PÓ, AÇÚCAR REFINADO E ADOÇANTE), UNIFORMES E PRODUTOS E INSUMOS NECESSÁRIOS PARA O DESENVOLVIMENTO DAS ATIVIDADES, A SEREM EXECUTADOS EM REGIME DE DEDICAÇÃO EXCLUSIVA DE MÃO DE OBRA, A SEREM EXECUTADOS COM REGIME DE DEDICAÇÃO EXCLUSIVA DE MÃO DE OBRA, NAS CONDIÇÕES ESTABELECIDAS NO TERMO DE REFERÊNCIA', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 28 - Inciso: I', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '113602', 'licitacao_numero': '90026/2024', 'sistema_origem_licitacao': None, 'data_assinatura': '2024-08-02', 'data_publicacao': '2024-08-05', 'data_proposta_comercial': '2024-07-11', 'vigencia_inicio': '2024-09-03', 'vigencia_fim': '2025-09-03', 'valor_inicial': '156.306,60', 'valor_global': '156.306,60', 'num_parcelas': 1, 'valor_parcela': '156.306,60', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '20.591.265/0001-19', 'fornecedor_nome': 'GRUPO OG SERVICOS E VIGILANCIA LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/295851/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/295851/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/295851/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/295851/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/295851/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/295851/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/295851/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/295851/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/295851/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/295851/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/295851/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/295851/arquivos'}, {'id': 299366, 'receita_despesa': 'Despesa', 'numero': '00004/2024', 'codigo_tipo': '50', 'tipo': 'Contrato', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.000307/2024-74', 'objeto': 'CONTRATAÇÃO DE SERVIÇOS COMUNS DE TÁXI CONVENCIONAL, A SEREM REALIZADOS SOB DEMANDA, MEDIANTE “VOUCHER” EM PAPEL E OU/ELETRÔNICO E OU/ APLICATIVO, PARA SUPRIR A NECESSIDADE DE TRANSPORTE TERRESTRE DE SERVIDORES, COLABORADORES, ESTAGIÁRIOS E AUTORIDADES DA UNIDADE DESCENTRALIZADA DO IPEA NO RIO DE JANEIRO/RJ, NO ÂMBITO DO MUNICÍPIO DO RIO DE JANEIRO E SUA REGIÃO METROPOLITANA, EXCLUSIVAMENTE A SERVIÇO, A FIM DE ATENDER ÀS NECESSIDADES DO IPEA/RJ', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '90043/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2024-08-13', 'data_publicacao': '2024-08-14', 'data_proposta_comercial': '2024-07-29', 'vigencia_inicio': '2024-09-17', 'vigencia_fim': '2025-09-17', 'valor_inicial': '9.504,00', 'valor_global': '9.504,00', 'num_parcelas': 12, 'valor_parcela': '792,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '48.442.981/0001-53', 'fornecedor_nome': 'CCRJ AGENCIAMENTO DE TRANSPORTE LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/299366/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/299366/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/299366/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/299366/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/299366/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/299366/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/299366/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/299366/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/299366/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/299366/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/299366/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/299366/arquivos'}, {'id': 305749, 'receita_despesa': 'Despesa', 'numero': '2024NE000021', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.000270/2024-84', 'objeto': 'SERVIÇOS DE RECARGA E MANUTENÇÃO DOS EXTINTORES DE COMBATE A INCÊNDIO DO IPEA/RJ', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '90031/2024', 'sistema_origem_licitacao': None, 'data_assinatura': '2024-05-20', 'data_publicacao': '2024-08-30', 'data_proposta_comercial': '2024-04-15', 'vigencia_inicio': '2024-05-21', 'vigencia_fim': '2024-12-31', 'valor_inicial': '721,19', 'valor_global': '721,19', 'num_parcelas': 1, 'valor_parcela': '721,19', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '22.159.233/0001-74', 'fornecedor_nome': 'T. V. DA SILVA SERVICOS E EQUIPAMENTOS CONTRA INCENDIO', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/305749/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/305749/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/305749/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/305749/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/305749/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/305749/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/305749/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/305749/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/305749/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/305749/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/305749/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/305749/arquivos'}, {'id': 306448, 'receita_despesa': 'Despesa', 'numero': '2024NE000014', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'Biblioteca', 'processo': '03001.003685/2023-29', 'objeto': 'ASSINATURA DA VERSÃO DIGITAL DA REVISTA THE ECONOMIST PARA O EXERCÍCIO DE 2024', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 74 - Inciso: I', 'informacao_complementar': None, 'codigo_modalidade': '07', 'modalidade': 'Inexigibilidade', 'unidade_compra': '113602', 'licitacao_numero': '90030/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2024-02-20', 'data_publicacao': '2024-09-03', 'data_proposta_comercial': '2024-02-19', 'vigencia_inicio': '2024-02-21', 'vigencia_fim': '2024-12-31', 'valor_inicial': '1.122,43', 'valor_global': '1.122,43', 'num_parcelas': 1, 'valor_parcela': '1.122,43', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '07.492.558/0001-80', 'fornecedor_nome': 'MEDIA INTERNATIONAL DISTRIBUICAO DE PUBLICACOES LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/306448/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/306448/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/306448/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/306448/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/306448/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/306448/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/306448/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/306448/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/306448/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/306448/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/306448/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/306448/arquivos'}, {'id': 314774, 'receita_despesa': 'Despesa', 'numero': '00005/2024', 'codigo_tipo': '50', 'tipo': 'Contrato', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'SEINF', 'processo': '03001.002678/2023-18', 'objeto': 'CONTRATAÇÃO DE SERVIÇOS COMUNS DE MANUTENÇÃO PREVENTIVA E CORRETIVA DOS EQUIPAMENTOS DE AR-CONDICIONADO DO IPEA/RJ, SEM MÃO DE OBRA EXCLUSIVA, COM O SERVIÇO DE MANUTENÇÃO CORRETIVA E O FORNECIMENTO DE PARTES E PEÇAS EXECUTADOS SOB DEMANDA', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 28 - Inciso: I', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '113602', 'licitacao_numero': '90050/2024', 'sistema_origem_licitacao': None, 'data_assinatura': '2024-09-20', 'data_publicacao': '2024-09-23', 'data_proposta_comercial': '2024-08-06', 'vigencia_inicio': '2024-10-02', 'vigencia_fim': '2029-10-02', 'valor_inicial': '143.350,00', 'valor_global': '143.350,00', 'num_parcelas': 60, 'valor_parcela': '2.389,17', 'valor_acumulado': '143.350,20', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '20.512.135/0001-43', 'fornecedor_nome': 'ECO-ICE SERVICOS DE REFRIGERACAO LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/314774/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/314774/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/314774/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/314774/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/314774/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/314774/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/314774/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/314774/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/314774/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/314774/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/314774/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/314774/arquivos'}, {'id': 321529, 'receita_despesa': 'Despesa', 'numero': '2024NE000029', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'COACC E COCCT', 'processo': '03001.002930/2024-61', 'objeto': 'PARTICIPAÇÃO DOS SERVIDORES ISABEL VIRGINIA DE ALENCAR PIRES E MANOEL DE RIBAMAR CARDOSO BARROSO NO CURSO DE \"INTELIGÊNCIA ARTIFICIAL APLICADA ÀS CONTRATAÇÕES PÚBLICAS\", PROMOVIDO PELA CONSULTRE CONSULTORIA E TREINAMENTO LTDA-EPP,CNPJ: 36.003.671/0001-53, A SER REALIZADO NOS DIAS 06/11 A 08/11/2024, DE FORMA PRESENCIAL, NO LOCAL DE REALIZAÇÃO: ROYAL JARDINS BOUTIQUE HOTEL - ALAMEDA JAÚ Nº 729 - JARDIM PAULISTA - SÃO PAULO-SP', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 74 - Inciso: III - Alinea: F', 'informacao_complementar': None, 'codigo_modalidade': '07', 'modalidade': 'Inexigibilidade', 'unidade_compra': '113602', 'licitacao_numero': '90032/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2024-10-03', 'data_publicacao': '2024-10-09', 'data_proposta_comercial': '2024-08-14', 'vigencia_inicio': '2024-10-03', 'vigencia_fim': '2024-12-04', 'valor_inicial': '8.380,00', 'valor_global': '8.380,00', 'num_parcelas': 1, 'valor_parcela': '8.380,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '36.003.671/0001-53', 'fornecedor_nome': 'CONSULTRE CONSULTORIA E TREINAMENTO LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/321529/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/321529/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/321529/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/321529/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/321529/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/321529/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/321529/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/321529/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/321529/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/321529/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/321529/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/321529/arquivos'}]\n" + ] + } + ], + "source": [ + "from pandas import json_normalize\n", + "flattened = json_normalize(response, sep='_').to_dict(orient='records')\n", + "print(flattened)" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "b20be2db3e6e3d8", + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-09T20:17:00.536766Z", + "start_time": "2025-01-09T20:17:00.533058Z" + } + }, + "outputs": [], + "source": [ + "for record in flattened:\n", + " for key, value in record.items():\n", + " if type(value) is list:\n", + " print(f\"{key}: {value}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "8c7f614db1672dd3", + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-09T20:07:26.908497Z", + "start_time": "2025-01-09T20:07:24.646656Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[\u001B[34m2025-01-09T21:07:25.922+0100\u001B[0m] {\u001B[34mcliente_base.py:\u001B[0m44} WARNING\u001B[0m - API falhou com status Unknown\u001B[0m\n", + "[\u001B[34m2025-01-09T21:07:26.902+0100\u001B[0m] {\u001B[34m_client.py:\u001B[0m1025} INFO\u001B[0m - HTTP Request: GET https://contratos.comprasnet.gov.br/api/contrato/4153/cronograma \"HTTP/1.1 200 OK\"\u001B[0m\n" + ] + } + ], + "source": [ + "client = ClienteContratos()\n", + "response = client.get_cronograma_by_contrato_id(contrato_id = '4153')" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "a807e23fd56534a2", + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-09T20:07:32.312139Z", + "start_time": "2025-01-09T20:07:32.302637Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'id': 1026018,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Acordo de Cooperação Técnica (ACT)',\n", + " 'numero': '00014/2019',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CELEBRAÇÃO DO CONTRATO: 0014/2019 DE ACORDO COM PROCESSO NÚMERO: 03001.002870/2019-10',\n", + " 'mesref': '02',\n", + " 'anoref': '2020',\n", + " 'vencimento': '2020-03-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182042,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '07',\n", + " 'anoref': '2020',\n", + " 'vencimento': '2020-08-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182043,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '08',\n", + " 'anoref': '2020',\n", + " 'vencimento': '2020-09-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182044,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '09',\n", + " 'anoref': '2020',\n", + " 'vencimento': '2020-10-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182045,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '10',\n", + " 'anoref': '2020',\n", + " 'vencimento': '2020-11-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182046,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '11',\n", + " 'anoref': '2020',\n", + " 'vencimento': '2020-12-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182047,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '12',\n", + " 'anoref': '2020',\n", + " 'vencimento': '2021-01-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182048,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '01',\n", + " 'anoref': '2021',\n", + " 'vencimento': '2021-02-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182049,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '02',\n", + " 'anoref': '2021',\n", + " 'vencimento': '2021-03-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182050,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '03',\n", + " 'anoref': '2021',\n", + " 'vencimento': '2021-04-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182051,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '04',\n", + " 'anoref': '2021',\n", + " 'vencimento': '2021-05-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182052,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '05',\n", + " 'anoref': '2021',\n", + " 'vencimento': '2021-06-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182053,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '06',\n", + " 'anoref': '2021',\n", + " 'vencimento': '2021-07-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182054,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '07',\n", + " 'anoref': '2021',\n", + " 'vencimento': '2021-08-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182055,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '08',\n", + " 'anoref': '2021',\n", + " 'vencimento': '2021-09-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182056,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '09',\n", + " 'anoref': '2021',\n", + " 'vencimento': '2021-10-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182057,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '10',\n", + " 'anoref': '2021',\n", + " 'vencimento': '2021-11-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182058,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '11',\n", + " 'anoref': '2021',\n", + " 'vencimento': '2021-12-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182059,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '12',\n", + " 'anoref': '2021',\n", + " 'vencimento': '2022-01-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182060,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '01',\n", + " 'anoref': '2022',\n", + " 'vencimento': '2022-02-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182061,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '02',\n", + " 'anoref': '2022',\n", + " 'vencimento': '2022-03-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182062,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '03',\n", + " 'anoref': '2022',\n", + " 'vencimento': '2022-04-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182063,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '04',\n", + " 'anoref': '2022',\n", + " 'vencimento': '2022-05-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182064,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '05',\n", + " 'anoref': '2022',\n", + " 'vencimento': '2022-06-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182065,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '06',\n", + " 'anoref': '2022',\n", + " 'vencimento': '2022-07-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182066,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '07',\n", + " 'anoref': '2022',\n", + " 'vencimento': '2022-08-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182067,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '08',\n", + " 'anoref': '2022',\n", + " 'vencimento': '2022-09-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182068,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '09',\n", + " 'anoref': '2022',\n", + " 'vencimento': '2022-10-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182069,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '10',\n", + " 'anoref': '2022',\n", + " 'vencimento': '2022-11-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182070,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '11',\n", + " 'anoref': '2022',\n", + " 'vencimento': '2022-12-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182071,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '12',\n", + " 'anoref': '2022',\n", + " 'vencimento': '2023-01-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182072,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '01',\n", + " 'anoref': '2023',\n", + " 'vencimento': '2023-02-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182073,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '02',\n", + " 'anoref': '2023',\n", + " 'vencimento': '2023-03-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182074,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '03',\n", + " 'anoref': '2023',\n", + " 'vencimento': '2023-04-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182075,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '04',\n", + " 'anoref': '2023',\n", + " 'vencimento': '2023-05-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182076,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '05',\n", + " 'anoref': '2023',\n", + " 'vencimento': '2023-06-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182077,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '06',\n", + " 'anoref': '2023',\n", + " 'vencimento': '2023-07-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182078,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '07',\n", + " 'anoref': '2023',\n", + " 'vencimento': '2023-08-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182079,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '08',\n", + " 'anoref': '2023',\n", + " 'vencimento': '2023-09-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182080,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '09',\n", + " 'anoref': '2023',\n", + " 'vencimento': '2023-10-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182081,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '10',\n", + " 'anoref': '2023',\n", + " 'vencimento': '2023-11-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182082,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '11',\n", + " 'anoref': '2023',\n", + " 'vencimento': '2023-12-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182083,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '12',\n", + " 'anoref': '2023',\n", + " 'vencimento': '2024-01-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182084,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '01',\n", + " 'anoref': '2024',\n", + " 'vencimento': '2024-02-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182085,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '02',\n", + " 'anoref': '2024',\n", + " 'vencimento': '2024-03-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182086,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '03',\n", + " 'anoref': '2024',\n", + " 'vencimento': '2024-04-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182087,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '04',\n", + " 'anoref': '2024',\n", + " 'vencimento': '2024-05-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182088,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '05',\n", + " 'anoref': '2024',\n", + " 'vencimento': '2024-06-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182089,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '06',\n", + " 'anoref': '2024',\n", + " 'vencimento': '2024-07-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182090,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '07',\n", + " 'anoref': '2024',\n", + " 'vencimento': '2024-08-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182091,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '08',\n", + " 'anoref': '2024',\n", + " 'vencimento': '2024-09-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182092,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '09',\n", + " 'anoref': '2024',\n", + " 'vencimento': '2024-10-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182093,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '10',\n", + " 'anoref': '2024',\n", + " 'vencimento': '2024-11-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182094,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '11',\n", + " 'anoref': '2024',\n", + " 'vencimento': '2024-12-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182095,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '12',\n", + " 'anoref': '2024',\n", + " 'vencimento': '2025-01-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182096,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '01',\n", + " 'anoref': '2025',\n", + " 'vencimento': '2025-02-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'},\n", + " {'id': 1182097,\n", + " 'contrato_id': 4153,\n", + " 'tipo': 'Termo de Apostilamento',\n", + " 'numero': '00001/2020',\n", + " 'receita_despesa': 'Despesa',\n", + " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", + " 'mesref': '02',\n", + " 'anoref': '2025',\n", + " 'vencimento': '2025-03-01',\n", + " 'retroativo': 'Não',\n", + " 'valor': '0,01'}]" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "response" + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-26T12:02:14.004473Z", + "start_time": "2025-01-26T12:02:12.856870Z" + } + }, + "cell_type": "code", + "source": [ + "from requests.auth import HTTPBasicAuth\n", + "from zeep import Transport, Client\n", + "from requests import Session\n", + "\n", + "BEARER_ENDPOINT = \"https://apigateway.conectagov.estaleiro.serpro.gov.br/oauth2/jwt-token/\"\n", + "SOAP_ENDPOINT = \"https://apigateway.conectagov.estaleiro.serpro.gov.br/api-consulta-siape/v1/consulta-siape\"\n", + "\n", + "session = Session()\n", + "session.auth = HTTPBasicAuth('9b47d48f-26fe-49d8-8c74-0a3d91bb06b0', '9828538e-7181-4a84-9f14-d0d3a08b8125')\n", + "transport = Transport(session=session)\n", + "bearer_client = Client(BEARER_ENDPOINT, transport=transport)" + ], + "id": "755fe8e7ee9bb871", + "outputs": [ + { + "ename": "HTTPError", + "evalue": "403 Client Error: Forbidden for url: https://apigateway.conectagov.estaleiro.serpro.gov.br/oauth2/jwt-token/", + "output_type": "error", + "traceback": [ + "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", + "\u001B[0;31mHTTPError\u001B[0m Traceback (most recent call last)", + "Cell \u001B[0;32mIn[3], line 11\u001B[0m\n\u001B[1;32m 9\u001B[0m session\u001B[38;5;241m.\u001B[39mauth \u001B[38;5;241m=\u001B[39m HTTPBasicAuth(\u001B[38;5;124m'\u001B[39m\u001B[38;5;124m9b47d48f-26fe-49d8-8c74-0a3d91bb06b0\u001B[39m\u001B[38;5;124m'\u001B[39m, \u001B[38;5;124m'\u001B[39m\u001B[38;5;124m9828538e-7181-4a84-9f14-d0d3a08b8125\u001B[39m\u001B[38;5;124m'\u001B[39m)\n\u001B[1;32m 10\u001B[0m transport \u001B[38;5;241m=\u001B[39m Transport(session\u001B[38;5;241m=\u001B[39msession)\n\u001B[0;32m---> 11\u001B[0m bearer_client \u001B[38;5;241m=\u001B[39m \u001B[43mClient\u001B[49m\u001B[43m(\u001B[49m\u001B[43mBEARER_ENDPOINT\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mtransport\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mtransport\u001B[49m\u001B[43m)\u001B[49m\n", + "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/site-packages/zeep/client.py:76\u001B[0m, in \u001B[0;36mClient.__init__\u001B[0;34m(self, wsdl, wsse, transport, service_name, port_name, plugins, settings)\u001B[0m\n\u001B[1;32m 74\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mwsdl \u001B[38;5;241m=\u001B[39m wsdl\n\u001B[1;32m 75\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[0;32m---> 76\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mwsdl \u001B[38;5;241m=\u001B[39m \u001B[43mDocument\u001B[49m\u001B[43m(\u001B[49m\u001B[43mwsdl\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mtransport\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43msettings\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43msettings\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 77\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mwsse \u001B[38;5;241m=\u001B[39m wsse\n\u001B[1;32m 78\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mplugins \u001B[38;5;241m=\u001B[39m plugins \u001B[38;5;28;01mif\u001B[39;00m plugins \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m \u001B[38;5;28;01melse\u001B[39;00m []\n", + "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/site-packages/zeep/wsdl/wsdl.py:86\u001B[0m, in \u001B[0;36mDocument.__init__\u001B[0;34m(self, location, transport, base, settings)\u001B[0m\n\u001B[1;32m 77\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_definitions \u001B[38;5;241m=\u001B[39m (\n\u001B[1;32m 78\u001B[0m {}\n\u001B[1;32m 79\u001B[0m ) \u001B[38;5;66;03m# type: typing.Dict[typing.Tuple[str, str], \"Definition\"]\u001B[39;00m\n\u001B[1;32m 80\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mtypes \u001B[38;5;241m=\u001B[39m Schema(\n\u001B[1;32m 81\u001B[0m node\u001B[38;5;241m=\u001B[39m\u001B[38;5;28;01mNone\u001B[39;00m,\n\u001B[1;32m 82\u001B[0m transport\u001B[38;5;241m=\u001B[39m\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mtransport,\n\u001B[1;32m 83\u001B[0m location\u001B[38;5;241m=\u001B[39m\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mlocation,\n\u001B[1;32m 84\u001B[0m settings\u001B[38;5;241m=\u001B[39m\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39msettings,\n\u001B[1;32m 85\u001B[0m )\n\u001B[0;32m---> 86\u001B[0m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mload\u001B[49m\u001B[43m(\u001B[49m\u001B[43mlocation\u001B[49m\u001B[43m)\u001B[49m\n", + "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/site-packages/zeep/wsdl/wsdl.py:89\u001B[0m, in \u001B[0;36mDocument.load\u001B[0;34m(self, location)\u001B[0m\n\u001B[1;32m 88\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;21mload\u001B[39m(\u001B[38;5;28mself\u001B[39m, location):\n\u001B[0;32m---> 89\u001B[0m document \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_get_xml_document\u001B[49m\u001B[43m(\u001B[49m\u001B[43mlocation\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 91\u001B[0m root_definitions \u001B[38;5;241m=\u001B[39m Definition(\u001B[38;5;28mself\u001B[39m, document, \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mlocation)\n\u001B[1;32m 92\u001B[0m root_definitions\u001B[38;5;241m.\u001B[39mresolve_imports()\n", + "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/site-packages/zeep/wsdl/wsdl.py:149\u001B[0m, in \u001B[0;36mDocument._get_xml_document\u001B[0;34m(self, location)\u001B[0m\n\u001B[1;32m 141\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;21m_get_xml_document\u001B[39m(\u001B[38;5;28mself\u001B[39m, location: typing\u001B[38;5;241m.\u001B[39mIO) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m etree\u001B[38;5;241m.\u001B[39m_Element:\n\u001B[1;32m 142\u001B[0m \u001B[38;5;250m \u001B[39m\u001B[38;5;124;03m\"\"\"Load the XML content from the given location and return an\u001B[39;00m\n\u001B[1;32m 143\u001B[0m \u001B[38;5;124;03m lxml.Element object.\u001B[39;00m\n\u001B[1;32m 144\u001B[0m \n\u001B[0;32m (...)\u001B[0m\n\u001B[1;32m 147\u001B[0m \n\u001B[1;32m 148\u001B[0m \u001B[38;5;124;03m \"\"\"\u001B[39;00m\n\u001B[0;32m--> 149\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mload_external\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m 150\u001B[0m \u001B[43m \u001B[49m\u001B[43mlocation\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mtransport\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mlocation\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43msettings\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43msettings\u001B[49m\n\u001B[1;32m 151\u001B[0m \u001B[43m \u001B[49m\u001B[43m)\u001B[49m\n", + "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/site-packages/zeep/loader.py:89\u001B[0m, in \u001B[0;36mload_external\u001B[0;34m(url, transport, base_url, settings)\u001B[0m\n\u001B[1;32m 87\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m base_url:\n\u001B[1;32m 88\u001B[0m url \u001B[38;5;241m=\u001B[39m absolute_location(url, base_url)\n\u001B[0;32m---> 89\u001B[0m content \u001B[38;5;241m=\u001B[39m \u001B[43mtransport\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mload\u001B[49m\u001B[43m(\u001B[49m\u001B[43murl\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 90\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m parse_xml(content, transport, base_url, settings\u001B[38;5;241m=\u001B[39msettings)\n", + "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/site-packages/zeep/transports.py:122\u001B[0m, in \u001B[0;36mTransport.load\u001B[0;34m(self, url)\u001B[0m\n\u001B[1;32m 119\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m response:\n\u001B[1;32m 120\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mbytes\u001B[39m(response)\n\u001B[0;32m--> 122\u001B[0m content \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_load_remote_data\u001B[49m\u001B[43m(\u001B[49m\u001B[43murl\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 124\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mcache:\n\u001B[1;32m 125\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mcache\u001B[38;5;241m.\u001B[39madd(url, content)\n", + "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/site-packages/zeep/transports.py:136\u001B[0m, in \u001B[0;36mTransport._load_remote_data\u001B[0;34m(self, url)\u001B[0m\n\u001B[1;32m 134\u001B[0m response \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39msession\u001B[38;5;241m.\u001B[39mget(url, timeout\u001B[38;5;241m=\u001B[39m\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mload_timeout)\n\u001B[1;32m 135\u001B[0m \u001B[38;5;28;01mwith\u001B[39;00m closing(response):\n\u001B[0;32m--> 136\u001B[0m \u001B[43mresponse\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mraise_for_status\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 137\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m response\u001B[38;5;241m.\u001B[39mcontent\n", + "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/site-packages/requests/models.py:1024\u001B[0m, in \u001B[0;36mResponse.raise_for_status\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 1019\u001B[0m http_error_msg \u001B[38;5;241m=\u001B[39m (\n\u001B[1;32m 1020\u001B[0m \u001B[38;5;124mf\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;132;01m{\u001B[39;00m\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mstatus_code\u001B[38;5;132;01m}\u001B[39;00m\u001B[38;5;124m Server Error: \u001B[39m\u001B[38;5;132;01m{\u001B[39;00mreason\u001B[38;5;132;01m}\u001B[39;00m\u001B[38;5;124m for url: \u001B[39m\u001B[38;5;132;01m{\u001B[39;00m\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39murl\u001B[38;5;132;01m}\u001B[39;00m\u001B[38;5;124m\"\u001B[39m\n\u001B[1;32m 1021\u001B[0m )\n\u001B[1;32m 1023\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m http_error_msg:\n\u001B[0;32m-> 1024\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m HTTPError(http_error_msg, response\u001B[38;5;241m=\u001B[39m\u001B[38;5;28mself\u001B[39m)\n", + "\u001B[0;31mHTTPError\u001B[0m: 403 Client Error: Forbidden for url: https://apigateway.conectagov.estaleiro.serpro.gov.br/oauth2/jwt-token/" + ] + } + ], + "execution_count": 3 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-27T08:26:47.857897Z", + "start_time": "2025-01-27T08:26:46.737498Z" + } + }, + "cell_type": "code", + "source": [ + "# SIAPE\n", + "\n", + "from zeep import Transport, Client\n", + "from requests import Session\n", + "import requests\n", + "\n", + "# Auth endpoint for token\n", + "BEARER_ENDPOINT = \"https://apigateway.conectagov.estaleiro.serpro.gov.br/oauth2/jwt-token/\"\n", + "SOAP_ENDPOINT = \"https://apigateway.conectagov.estaleiro.serpro.gov.br/api-consulta-siape/v1/consulta-siape\"\n", + "\n", + "# Get token\n", + "auth_response = requests.get(\n", + " BEARER_ENDPOINT,\n", + " auth=('9b47d48f-26fe-49d8-8c74-0a3d91bb06b0', '9828538e-7181-4a84-9f14-d0d3a08b8125'),\n", + " headers={'Content-Type': 'application/x-www-form-urlencoded'}\n", + ")\n", + "print(auth_response.text)\n", + "token = auth_response.json()['access_token'] # Adjust based on response format\n", + "\n", + "# Setup SOAP client with token\n", + "session = Session()\n", + "session.headers.update({'Authorization': f'Bearer {token}'})\n", + "transport = Transport(session=session)\n", + "\n", + "# Create SOAP client\n", + "client = Client(SOAP_ENDPOINT, transport=transport)\n", + "\n", + "# Make service calls\n", + "result = client.service.method_name()" + ], + "id": "e0e543b67f021fb7", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "

403 Forbidden

\n", + "Request forbidden by administrative rules.\n", + "\n", + "\n" + ] + }, + { + "ename": "JSONDecodeError", + "evalue": "Expecting value: line 1 column 1 (char 0)", + "output_type": "error", + "traceback": [ + "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", + "\u001B[0;31mJSONDecodeError\u001B[0m Traceback (most recent call last)", + "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/site-packages/requests/models.py:974\u001B[0m, in \u001B[0;36mResponse.json\u001B[0;34m(self, **kwargs)\u001B[0m\n\u001B[1;32m 973\u001B[0m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[0;32m--> 974\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mcomplexjson\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mloads\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mtext\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 975\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m JSONDecodeError \u001B[38;5;28;01mas\u001B[39;00m e:\n\u001B[1;32m 976\u001B[0m \u001B[38;5;66;03m# Catch JSON-related errors and raise as requests.JSONDecodeError\u001B[39;00m\n\u001B[1;32m 977\u001B[0m \u001B[38;5;66;03m# This aliases json.JSONDecodeError and simplejson.JSONDecodeError\u001B[39;00m\n", + "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/json/__init__.py:346\u001B[0m, in \u001B[0;36mloads\u001B[0;34m(s, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)\u001B[0m\n\u001B[1;32m 343\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m (\u001B[38;5;28mcls\u001B[39m \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m \u001B[38;5;129;01mand\u001B[39;00m object_hook \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m \u001B[38;5;129;01mand\u001B[39;00m\n\u001B[1;32m 344\u001B[0m parse_int \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m \u001B[38;5;129;01mand\u001B[39;00m parse_float \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m \u001B[38;5;129;01mand\u001B[39;00m\n\u001B[1;32m 345\u001B[0m parse_constant \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m \u001B[38;5;129;01mand\u001B[39;00m object_pairs_hook \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m \u001B[38;5;129;01mand\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m kw):\n\u001B[0;32m--> 346\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43m_default_decoder\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mdecode\u001B[49m\u001B[43m(\u001B[49m\u001B[43ms\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 347\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mcls\u001B[39m \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m:\n", + "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/json/decoder.py:337\u001B[0m, in \u001B[0;36mJSONDecoder.decode\u001B[0;34m(self, s, _w)\u001B[0m\n\u001B[1;32m 333\u001B[0m \u001B[38;5;250m\u001B[39m\u001B[38;5;124;03m\"\"\"Return the Python representation of ``s`` (a ``str`` instance\u001B[39;00m\n\u001B[1;32m 334\u001B[0m \u001B[38;5;124;03mcontaining a JSON document).\u001B[39;00m\n\u001B[1;32m 335\u001B[0m \n\u001B[1;32m 336\u001B[0m \u001B[38;5;124;03m\"\"\"\u001B[39;00m\n\u001B[0;32m--> 337\u001B[0m obj, end \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mraw_decode\u001B[49m\u001B[43m(\u001B[49m\u001B[43ms\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43midx\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m_w\u001B[49m\u001B[43m(\u001B[49m\u001B[43ms\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m0\u001B[39;49m\u001B[43m)\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mend\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 338\u001B[0m end \u001B[38;5;241m=\u001B[39m _w(s, end)\u001B[38;5;241m.\u001B[39mend()\n", + "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/json/decoder.py:355\u001B[0m, in \u001B[0;36mJSONDecoder.raw_decode\u001B[0;34m(self, s, idx)\u001B[0m\n\u001B[1;32m 354\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m \u001B[38;5;167;01mStopIteration\u001B[39;00m \u001B[38;5;28;01mas\u001B[39;00m err:\n\u001B[0;32m--> 355\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m JSONDecodeError(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mExpecting value\u001B[39m\u001B[38;5;124m\"\u001B[39m, s, err\u001B[38;5;241m.\u001B[39mvalue) \u001B[38;5;28;01mfrom\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;28;01mNone\u001B[39;00m\n\u001B[1;32m 356\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m obj, end\n", + "\u001B[0;31mJSONDecodeError\u001B[0m: Expecting value: line 1 column 1 (char 0)", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001B[0;31mJSONDecodeError\u001B[0m Traceback (most recent call last)", + "Cell \u001B[0;32mIn[60], line 18\u001B[0m\n\u001B[1;32m 12\u001B[0m auth_response \u001B[38;5;241m=\u001B[39m requests\u001B[38;5;241m.\u001B[39mget(\n\u001B[1;32m 13\u001B[0m BEARER_ENDPOINT,\n\u001B[1;32m 14\u001B[0m auth\u001B[38;5;241m=\u001B[39m(\u001B[38;5;124m'\u001B[39m\u001B[38;5;124m9b47d48f-26fe-49d8-8c74-0a3d91bb06b0\u001B[39m\u001B[38;5;124m'\u001B[39m, \u001B[38;5;124m'\u001B[39m\u001B[38;5;124m9828538e-7181-4a84-9f14-d0d3a08b8125\u001B[39m\u001B[38;5;124m'\u001B[39m),\n\u001B[1;32m 15\u001B[0m headers\u001B[38;5;241m=\u001B[39m{\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mContent-Type\u001B[39m\u001B[38;5;124m'\u001B[39m: \u001B[38;5;124m'\u001B[39m\u001B[38;5;124mapplication/x-www-form-urlencoded\u001B[39m\u001B[38;5;124m'\u001B[39m}\n\u001B[1;32m 16\u001B[0m )\n\u001B[1;32m 17\u001B[0m \u001B[38;5;28mprint\u001B[39m(auth_response\u001B[38;5;241m.\u001B[39mtext)\n\u001B[0;32m---> 18\u001B[0m token \u001B[38;5;241m=\u001B[39m \u001B[43mauth_response\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mjson\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m[\u001B[38;5;124m'\u001B[39m\u001B[38;5;124maccess_token\u001B[39m\u001B[38;5;124m'\u001B[39m] \u001B[38;5;66;03m# Adjust based on response format\u001B[39;00m\n\u001B[1;32m 20\u001B[0m \u001B[38;5;66;03m# Setup SOAP client with token\u001B[39;00m\n\u001B[1;32m 21\u001B[0m session \u001B[38;5;241m=\u001B[39m Session()\n", + "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/site-packages/requests/models.py:978\u001B[0m, in \u001B[0;36mResponse.json\u001B[0;34m(self, **kwargs)\u001B[0m\n\u001B[1;32m 974\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m complexjson\u001B[38;5;241m.\u001B[39mloads(\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mtext, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs)\n\u001B[1;32m 975\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m JSONDecodeError \u001B[38;5;28;01mas\u001B[39;00m e:\n\u001B[1;32m 976\u001B[0m \u001B[38;5;66;03m# Catch JSON-related errors and raise as requests.JSONDecodeError\u001B[39;00m\n\u001B[1;32m 977\u001B[0m \u001B[38;5;66;03m# This aliases json.JSONDecodeError and simplejson.JSONDecodeError\u001B[39;00m\n\u001B[0;32m--> 978\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m RequestsJSONDecodeError(e\u001B[38;5;241m.\u001B[39mmsg, e\u001B[38;5;241m.\u001B[39mdoc, e\u001B[38;5;241m.\u001B[39mpos)\n", + "\u001B[0;31mJSONDecodeError\u001B[0m: Expecting value: line 1 column 1 (char 0)" + ] + } + ], + "execution_count": 60 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-27T07:12:44.796378Z", + "start_time": "2025-01-27T07:12:43.165381Z" + } + }, + "cell_type": "code", + "source": [ + "#SIAFI\n", + "\n", + "BEARER_ENDPOINT = \"https://gateway.apiserpro.serpro.gov.br/token\"\n", + "BEARER_KEY = \"fiuUXaOm9JFdKSxnfvF4dJzP0gwa\"\n", + "BEARER_SECRET = \"TZxX2KOpuJ5o_oPABK1NgUV1X3Ea\"\n", + "\n", + "import requests\n", + "\n", + "def get_token(url, consumer_key, consumer_secret):\n", + " data = {\n", + " 'grant_type': 'client_credentials'\n", + " }\n", + "\n", + " response = requests.post(\n", + " url,\n", + " data=data,\n", + " auth=(consumer_key, consumer_secret)\n", + " )\n", + " return response.json()\n", + "\n", + "token_response = get_token(BEARER_ENDPOINT, BEARER_KEY, BEARER_SECRET)" + ], + "id": "7b4eb0b409c26f98", + "outputs": [], + "execution_count": 17 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-27T07:12:45.386983Z", + "start_time": "2025-01-27T07:12:45.384243Z" + } + }, + "cell_type": "code", + "source": "print(token_response)", + "id": "c866fa793feeaba7", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'access_token': 'eyJ4NXQiOiJNalJqWkRRMU1EQmtPR1JqWW1Jek9EVmxaRFEzWkdFeU1EVTVabU5rWldVeU9XUmhPRFZpTnciLCJraWQiOiJNRGN5WVdFNU16ZzVaVFJrT1dVME1XWTBNVE0xTkdJMllqbG1OVFZrT0dJd01UVmlORGRpWldJM1pEUmpZVEpsTTJaa05Ua3dNR1F3TWpZeVlXTmxNQV9SUzI1NiIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJhdXRlbnRpa3VzIiwiYXV0IjoiQVBQTElDQVRJT04iLCJhdWQiOiJmaXVVWGFPbTlKRmRLU3huZnZGNGRKelAwZ3dhIiwibmJmIjoxNzM3OTYxMjg3LCJhenAiOiJmaXVVWGFPbTlKRmRLU3huZnZGNGRKelAwZ3dhIiwic2NvcGUiOiJkZWZhdWx0IiwiaXNzIjoiaHR0cHM6XC9cL3B1Ymxpc2hlci5hcGlzZXJwcm8uc2VycHJvLmdvdi5icjo0NDNcL29hdXRoMlwvdG9rZW4iLCJyZWFsbSI6eyJzaWduaW5nX3RlbmFudCI6ImNhcmJvbi5zdXBlciJ9LCJleHAiOjE3Mzc5NjQ4ODcsImlhdCI6MTczNzk2MTI4NywianRpIjoiZTA3ZTZmZjEtMzcwNy00OWU4LTg5YzMtZThjODI5NTk3NDU5In0.F7mJhJJOU-nMrsfs9fUXQjw3TpgCCjoIOhhVIujoL52y1d479t0BtQ6KXSOPVVEN4Xf5rjgDlbreNHTcqSK_iBrBPI7uBrSBjn5-li1eXXWVzO7i-w0LuLfYNP30wGjHa9g3D_CoUgyGsSkD_zQ0C6VI24Y0jNQcT_s2dvZ5D97xdZZK_Nu4u8jf4Oz9EX0lfP8mkYubHWk6QDt7KZtXnaoQQZXlZyEwfoymTwd6j8Rc2PquxwefeIPxl1OSw6rGC61KO2G97CmtADmXj-2IqbCj57UHJop3RFXqSh1whKSLThejB4JIUOfoJT6T5L_YkGd8jnxNH9bTuvDGqPlm4A', 'scope': 'default', 'token_type': 'Bearer', 'expires_in': 2922}\n" + ] + } + ], + "execution_count": 18 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-27T07:38:09.493043Z", + "start_time": "2025-01-27T07:38:08.711518Z" + } + }, + "cell_type": "code", + "source": [ + "SIAFI_ENDPOINT = \"https://gateway.apiserpro.serpro.gov.br/api-integra-siafi/api/v2/nota-credito/113601/61201/2024/596765\"\n", + "SIAFI_CREDENTIAL = \"MjE1NTQyMDY4NDcuMTEzNjAxLlNJQUZJMjAyMw==\"\n", + "def get_data(url, bearer_token, credential):\n", + " headers = {\n", + " 'Authorization': f'Bearer {bearer_token}',\n", + " 'Content-Type': 'application/json',\n", + " 'x-credencial': credential,\n", + " }\n", + " response = requests.get(url, headers=headers)\n", + " print(response.text)\n", + " return response.json()\n", + "\n", + "data = get_data(SIAFI_ENDPOINT, token_response.get('access_token'), SIAFI_CREDENTIAL)\n", + "print(data)" + ], + "id": "69cf00b09543d49b", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\"codigo\":99,\"mensagem\":\"Requisição inválida\",\"erros\":[{\"code\":\"99\",\"value\":\"\"}],\"status\":400}\n", + "{'codigo': 99, 'mensagem': 'Requisição inválida', 'erros': [{'code': '99', 'value': ''}], 'status': 400}\n" + ] + } + ], + "execution_count": 58 + }, + { + "metadata": {}, + "cell_type": "code", + "outputs": [], + "execution_count": null, + "source": [ + "from datetime import date\n", + "import time\n", + "\n", + "start = date(2024, 1, 1)\n", + "end = date(2025, 1, 1)\n", + "d = start\n", + "while d < end:\n", + " f = format(d, '%d%b%y').upper()\n", + " print(f)\n", + " data = get_data(SIAFI_ENDPOINT.format(date=f), token_response.get('access_token'), SIAFI_CREDENTIAL)\n", + " print(data)\n", + " time.sleep(10)" + ], + "id": "59bbda0290870f91" + }, + { + "metadata": {}, + "cell_type": "code", + "outputs": [], + "execution_count": null, + "source": "", + "id": "9bcadad5c695538a" + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pyproject.toml b/pyproject.toml index 025e8920..6d82bb46 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,6 @@ [tool.poetry] -name = "app_lappis_ipea" +package-mode = false +name = "lappis" version = "0.1.0" description = "Gestão de Dados do IPEA - Aplicações Python" authors = ["Lappis UNB"] @@ -13,6 +14,7 @@ dbt-postgres = "*" pandas = "*" requests = "*" sqlalchemy = "*" +zeep = "*" [tool.poetry.group.dev.dependencies] black = "*" @@ -70,6 +72,7 @@ ignore = [] [tool.mypy] python_version = "3.11" +files = ["**/*.py"] warn_return_any = true warn_unused_configs = true disallow_untyped_defs = true @@ -81,6 +84,7 @@ warn_redundant_casts = true warn_unused_ignores = true warn_no_return = true warn_unreachable = true +disable_error_code = ["arg-type"] [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/requirements.txt b/requirements.txt index f7ebb449..5f433440 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ agate==1.9.1 ; python_version >= "3.11" and python_version < "3.12" aiohappyeyeballs==2.4.4 ; python_version >= "3.11" and python_version < "3.12" aiohttp==3.10.11 ; python_version >= "3.11" and python_version < "3.12" aiosignal==1.3.2 ; python_version >= "3.11" and python_version < "3.12" -alembic==1.14.0 ; python_version >= "3.11" and python_version < "3.12" +alembic==1.14.1 ; python_version >= "3.11" and python_version < "3.12" annotated-types==0.7.0 ; python_version >= "3.11" and python_version < "3.12" anyio==4.8.0 ; python_version >= "3.11" and python_version < "3.12" apache-airflow-providers-common-io==1.4.2 ; python_version >= "3.11" and python_version < "3.12" @@ -15,7 +15,7 @@ apache-airflow==2.8.1 ; python_version >= "3.11" and python_version < "3.12" apispec[yaml]==6.8.1 ; python_version >= "3.11" and python_version < "3.12" argcomplete==3.5.3 ; python_version >= "3.11" and python_version < "3.12" asgiref==3.8.1 ; python_version >= "3.11" and python_version < "3.12" -attrs==24.3.0 ; python_version >= "3.11" and python_version < "3.12" +attrs==25.1.0 ; python_version >= "3.11" and python_version < "3.12" babel==2.16.0 ; python_version >= "3.11" and python_version < "3.12" blinker==1.9.0 ; python_version >= "3.11" and python_version < "3.12" cachelib==0.9.0 ; python_version >= "3.11" and python_version < "3.12" @@ -32,14 +32,14 @@ cron-descriptor==1.4.5 ; python_version >= "3.11" and python_version < "3.12" croniter==6.0.0 ; python_version >= "3.11" and python_version < "3.12" cryptography==44.0.0 ; python_version >= "3.11" and python_version < "3.12" daff==1.3.46 ; python_version >= "3.11" and python_version < "3.12" -dbt-adapters==1.13.0 ; python_version >= "3.11" and python_version < "3.12" +dbt-adapters==1.13.2 ; python_version >= "3.11" and python_version < "3.12" dbt-common==1.14.0 ; python_version >= "3.11" and python_version < "3.12" dbt-core==1.9.1 ; python_version >= "3.11" and python_version < "3.12" dbt-extractor==0.5.1 ; python_version >= "3.11" and python_version < "3.12" dbt-postgres==1.9.0 ; python_version >= "3.11" and python_version < "3.12" dbt-semantic-interfaces==0.7.4 ; python_version >= "3.11" and python_version < "3.12" deepdiff==7.0.1 ; python_version >= "3.11" and python_version < "3.12" -deprecated==1.2.15 ; python_version >= "3.11" and python_version < "3.12" +deprecated==1.2.17 ; python_version >= "3.11" and python_version < "3.12" dill==0.3.9 ; python_version >= "3.11" and python_version < "3.12" dnspython==2.7.0 ; python_version >= "3.11" and python_version < "3.12" email-validator==1.3.1 ; python_version >= "3.11" and python_version < "3.12" @@ -47,7 +47,7 @@ flask-appbuilder==4.3.10 ; python_version >= "3.11" and python_version < "3.12" flask-babel==2.0.0 ; python_version >= "3.11" and python_version < "3.12" flask-caching==2.3.0 ; python_version >= "3.11" and python_version < "3.12" flask-jwt-extended==4.7.1 ; python_version >= "3.11" and python_version < "3.12" -flask-limiter==3.10.0 ; python_version >= "3.11" and python_version < "3.12" +flask-limiter==3.10.1 ; python_version >= "3.11" and python_version < "3.12" flask-login==0.6.3 ; python_version >= "3.11" and python_version < "3.12" flask-session==0.8.0 ; python_version >= "3.11" and python_version < "3.12" flask-sqlalchemy==2.5.1 ; python_version >= "3.11" and python_version < "3.12" @@ -58,7 +58,7 @@ fsspec==2024.12.0 ; python_version >= "3.11" and python_version < "3.12" google-re2==1.1.20240702 ; python_version >= "3.11" and python_version < "3.12" googleapis-common-protos==1.66.0 ; python_version >= "3.11" and python_version < "3.12" greenlet==3.1.1 ; python_version >= "3.11" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32") and python_version < "3.12" -grpcio==1.69.0 ; python_version >= "3.11" and python_version < "3.12" +grpcio==1.70.0 ; python_version >= "3.11" and python_version < "3.12" gunicorn==23.0.0 ; python_version >= "3.11" and python_version < "3.12" h11==0.14.0 ; python_version >= "3.11" and python_version < "3.12" httpcore==1.0.7 ; python_version >= "3.11" and python_version < "3.12" @@ -73,25 +73,26 @@ jsonschema-specifications==2024.10.1 ; python_version >= "3.11" and python_versi jsonschema==4.23.0 ; python_version >= "3.11" and python_version < "3.12" lazy-object-proxy==1.10.0 ; python_version >= "3.11" and python_version < "3.12" leather==0.4.0 ; python_version >= "3.11" and python_version < "3.12" -limits==4.0.0 ; python_version >= "3.11" and python_version < "3.12" +limits==4.0.1 ; python_version >= "3.11" and python_version < "3.12" linkify-it-py==2.0.3 ; python_version >= "3.11" and python_version < "3.12" lockfile==0.12.2 ; python_version >= "3.11" and python_version < "3.12" +lxml==5.3.0 ; python_version >= "3.11" and python_version < "3.12" mako==1.3.8 ; python_version >= "3.11" and python_version < "3.12" markdown-it-py==3.0.0 ; python_version >= "3.11" and python_version < "3.12" markdown==3.7 ; python_version >= "3.11" and python_version < "3.12" markupsafe==3.0.2 ; python_version >= "3.11" and python_version < "3.12" marshmallow-oneofschema==3.1.1 ; python_version >= "3.11" and python_version < "3.12" marshmallow-sqlalchemy==0.26.1 ; python_version >= "3.11" and python_version < "3.12" -marshmallow==3.25.0 ; python_version >= "3.11" and python_version < "3.12" +marshmallow==3.26.0 ; python_version >= "3.11" and python_version < "3.12" mashumaro[msgpack]==3.14 ; python_version >= "3.11" and python_version < "3.12" mdit-py-plugins==0.4.2 ; python_version >= "3.11" and python_version < "3.12" mdurl==0.1.2 ; python_version >= "3.11" and python_version < "3.12" -more-itertools==10.5.0 ; python_version >= "3.11" and python_version < "3.12" +more-itertools==10.6.0 ; python_version >= "3.11" and python_version < "3.12" msgpack==1.1.0 ; python_version >= "3.11" and python_version < "3.12" msgspec==0.19.0 ; python_version >= "3.11" and python_version < "3.12" multidict==6.1.0 ; python_version >= "3.11" and python_version < "3.12" networkx==3.4.2 ; python_version >= "3.11" and python_version < "3.12" -numpy==2.2.1 ; python_version == "3.11" +numpy==2.2.2 ; python_version == "3.11" opentelemetry-api==1.29.0 ; python_version >= "3.11" and python_version < "3.12" opentelemetry-exporter-otlp-proto-common==1.29.0 ; python_version >= "3.11" and python_version < "3.12" opentelemetry-exporter-otlp-proto-grpc==1.29.0 ; python_version >= "3.11" and python_version < "3.12" @@ -106,6 +107,7 @@ pandas==2.2.3 ; python_version >= "3.11" and python_version < "3.12" parsedatetime==2.6 ; python_version >= "3.11" and python_version < "3.12" pathspec==0.12.1 ; python_version >= "3.11" and python_version < "3.12" pendulum==3.0.0 ; python_version >= "3.11" and python_version < "3.12" +platformdirs==4.3.6 ; python_version >= "3.11" and python_version < "3.12" pluggy==1.5.0 ; python_version >= "3.11" and python_version < "3.12" prison==0.2.1 ; python_version >= "3.11" and python_version < "3.12" propcache==0.2.1 ; python_version >= "3.11" and python_version < "3.12" @@ -114,7 +116,7 @@ psutil==6.1.1 ; python_version >= "3.11" and python_version < "3.12" psycopg2-binary==2.9.10 ; python_version >= "3.11" and python_version < "3.12" pycparser==2.22 ; python_version >= "3.11" and python_version < "3.12" and platform_python_implementation != "PyPy" pydantic-core==2.27.2 ; python_version >= "3.11" and python_version < "3.12" -pydantic==2.10.5 ; python_version >= "3.11" and python_version < "3.12" +pydantic==2.10.6 ; python_version >= "3.11" and python_version < "3.12" pygments==2.19.1 ; python_version >= "3.11" and python_version < "3.12" pyjwt==2.10.1 ; python_version >= "3.11" and python_version < "3.12" python-daemon==3.1.2 ; python_version >= "3.11" and python_version < "3.12" @@ -124,7 +126,8 @@ python-slugify==8.0.4 ; python_version >= "3.11" and python_version < "3.12" pytimeparse==1.1.8 ; python_version >= "3.11" and python_version < "3.12" pytz==2024.2 ; python_version >= "3.11" and python_version < "3.12" pyyaml==6.0.2 ; python_version >= "3.11" and python_version < "3.12" -referencing==0.35.1 ; python_version >= "3.11" and python_version < "3.12" +referencing==0.36.2 ; python_version >= "3.11" and python_version < "3.12" +requests-file==2.1.0 ; python_version >= "3.11" and python_version < "3.12" requests-toolbelt==1.0.0 ; python_version >= "3.11" and python_version < "3.12" requests==2.32.3 ; python_version >= "3.11" and python_version < "3.12" rfc3339-validator==0.1.4 ; python_version >= "3.11" and python_version < "3.12" @@ -145,13 +148,14 @@ termcolor==2.5.0 ; python_version >= "3.11" and python_version < "3.12" text-unidecode==1.3 ; python_version >= "3.11" and python_version < "3.12" types-requests==2.32.0.20241016 ; python_version >= "3.11" and python_version < "3.12" typing-extensions==4.12.2 ; python_version >= "3.11" and python_version < "3.12" -tzdata==2024.2 ; python_version >= "3.11" and python_version < "3.12" +tzdata==2025.1 ; python_version >= "3.11" and python_version < "3.12" uc-micro-py==1.0.3 ; python_version >= "3.11" and python_version < "3.12" unicodecsv==0.14.1 ; python_version >= "3.11" and python_version < "3.12" universal-pathlib==0.2.6 ; python_version >= "3.11" and python_version < "3.12" urllib3==2.3.0 ; python_version >= "3.11" and python_version < "3.12" werkzeug==2.3.8 ; python_version >= "3.11" and python_version < "3.12" -wrapt==1.17.0 ; python_version >= "3.11" and python_version < "3.12" +wrapt==1.17.2 ; python_version >= "3.11" and python_version < "3.12" wtforms==3.2.1 ; python_version >= "3.11" and python_version < "3.12" yarl==1.18.3 ; python_version >= "3.11" and python_version < "3.12" +zeep==4.3.1 ; python_version >= "3.11" and python_version < "3.12" zipp==3.21.0 ; python_version >= "3.11" and python_version < "3.12" diff --git a/src/superset/__init__.py b/src/superset/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/jupyter/__init__.py b/superset/__init__.py similarity index 100% rename from src/jupyter/__init__.py rename to superset/__init__.py From b93a79e100905592fcec73295ee4447261f16efb Mon Sep 17 00:00:00 2001 From: VictorSzk Date: Tue, 28 Jan 2025 16:34:51 +0000 Subject: [PATCH 002/317] Chore/org pastas --- .user.yml | 1 + airflow/dags/__init__.py | 0 airflow/dags/dbt/.user.yml | 1 + airflow/dags/dbt/ipea/.user.yml | 1 + airflow/dags/dbt/ipea/dbt_project.yml | 32 ++++ .../dbt/ipea/macros/get_custom_schema.sql | 5 + .../models/contratos/bronze/contratos.sql | 88 ++++++++++ .../ipea/models/contratos/bronze/empenhos.sql | 38 +++++ .../contratos/bronze/empenhos_tesouro.sql | 36 ++++ .../ipea/models/contratos/bronze/estagios.sql | 74 +++++++++ .../ipea/models/contratos/bronze/faturas.sql | 49 ++++++ .../contratos/gold/contratos_resumo.sql | 35 ++++ .../contratos/silver/contratos_empenhos.sql | 156 ++++++++++++++++++ airflow/dags/dbt/ipea/profiles.yml | 11 ++ 14 files changed, 527 insertions(+) create mode 100644 .user.yml delete mode 100644 airflow/dags/__init__.py create mode 100644 airflow/dags/dbt/.user.yml create mode 100644 airflow/dags/dbt/ipea/.user.yml create mode 100644 airflow/dags/dbt/ipea/dbt_project.yml create mode 100644 airflow/dags/dbt/ipea/macros/get_custom_schema.sql create mode 100644 airflow/dags/dbt/ipea/models/contratos/bronze/contratos.sql create mode 100644 airflow/dags/dbt/ipea/models/contratos/bronze/empenhos.sql create mode 100644 airflow/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql create mode 100644 airflow/dags/dbt/ipea/models/contratos/bronze/estagios.sql create mode 100644 airflow/dags/dbt/ipea/models/contratos/bronze/faturas.sql create mode 100644 airflow/dags/dbt/ipea/models/contratos/gold/contratos_resumo.sql create mode 100644 airflow/dags/dbt/ipea/models/contratos/silver/contratos_empenhos.sql create mode 100644 airflow/dags/dbt/ipea/profiles.yml diff --git a/.user.yml b/.user.yml new file mode 100644 index 00000000..78630811 --- /dev/null +++ b/.user.yml @@ -0,0 +1 @@ +id: ad35bb68-68d6-445c-a5ef-dea5f0fe9986 diff --git a/airflow/dags/__init__.py b/airflow/dags/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/airflow/dags/dbt/.user.yml b/airflow/dags/dbt/.user.yml new file mode 100644 index 00000000..5af1e34e --- /dev/null +++ b/airflow/dags/dbt/.user.yml @@ -0,0 +1 @@ +id: aecd48ae-3ec9-4354-b785-bd2c463a92a7 diff --git a/airflow/dags/dbt/ipea/.user.yml b/airflow/dags/dbt/ipea/.user.yml new file mode 100644 index 00000000..43198208 --- /dev/null +++ b/airflow/dags/dbt/ipea/.user.yml @@ -0,0 +1 @@ +id: d025f823-c5b2-49c6-b826-226fb25f1ad8 diff --git a/airflow/dags/dbt/ipea/dbt_project.yml b/airflow/dags/dbt/ipea/dbt_project.yml new file mode 100644 index 00000000..1bf2d907 --- /dev/null +++ b/airflow/dags/dbt/ipea/dbt_project.yml @@ -0,0 +1,32 @@ +name: 'ipea' + +version: 1.0.0 +config-version: 2 + +profile: ipea + +model-paths: ["models"] +analysis-paths: ["analyses"] +test-paths: ["tests"] +seed-paths: ["seeds"] +macro-paths: ["macros"] +snapshot-paths: ["snapshots"] + +clean-targets: + - "target" + - "dbt_packages" + - "logs" + +models: + ipea: + contratos: + +materialized: table + +database: analytics + +schema: contratos + bronze: + +materialized: incremental + pessoas: + +materialized: table + +database: analytics + +schema: pessoas + diff --git a/airflow/dags/dbt/ipea/macros/get_custom_schema.sql b/airflow/dags/dbt/ipea/macros/get_custom_schema.sql new file mode 100644 index 00000000..21bc73c8 --- /dev/null +++ b/airflow/dags/dbt/ipea/macros/get_custom_schema.sql @@ -0,0 +1,5 @@ +-- built-in schema generator + +{% macro generate_schema_name(custom_schema_name, node) -%} + {{ generate_schema_name_for_env(custom_schema_name, node) }} +{%- endmacro %} \ No newline at end of file diff --git a/airflow/dags/dbt/ipea/models/contratos/bronze/contratos.sql b/airflow/dags/dbt/ipea/models/contratos/bronze/contratos.sql new file mode 100644 index 00000000..cabb32f1 --- /dev/null +++ b/airflow/dags/dbt/ipea/models/contratos/bronze/contratos.sql @@ -0,0 +1,88 @@ + + +WITH contratos_raw AS ( + SELECT + -- Conversão de tipos e formatação de colunas + CAST(id AS INT) AS id, + receita_despesa, + numero, + CAST(contratante_orgao_origem_codigo AS INT) AS contratante_orgao_origem_codigo, + contratante_orgao_origem_nome, + CAST(contratante_orgao_origem_unidade_gestora_origem_codigo AS INT) AS contratante_orgao_origem_unidade_gestora_origem_codigo, + contratante_orgao_origem_unidade_gestora_origem_nome_resumido, + contratante_orgao_origem_unidade_gestora_origem_nome, + contratante_orgao_origem_unidade_gestora_origem_sisg, + contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi, + contratante_orgao_origem_unidade_gestora_origem_utiliza_antecip, + CAST(contratante_orgao_codigo AS INT) AS contratante_orgao_codigo, + contratante_orgao_nome, + CAST(contratante_orgao_unidade_gestora_codigo AS INT) AS contratante_orgao_unidade_gestora_codigo, + contratante_orgao_unidade_gestora_nome_resumido, + contratante_orgao_unidade_gestora_nome, + contratante_orgao_unidade_gestora_sisg, + contratante_orgao_unidade_gestora_utiliza_siafi, + contratante_orgao_unidade_gestora_utiliza_antecipagov, + fornecedor_tipo, + REGEXP_REPLACE(fornecedor_cnpj_cpf_idgener, '[^0-9A-Za-z]', '', 'g') AS fornecedor_cnpj_cpf_idgener, + fornecedor_nome, + CAST(codigo_tipo AS INT) AS codigo_tipo, + tipo, + subtipo, + prorrogavel, + situacao, + justificativa_inativo, + categoria, + subcategoria, + unidades_requisitantes, + REGEXP_REPLACE(processo, '[^0-9A-Za-z]', '', 'g') AS processo, + objeto, + amparo_legal, + informacao_complementar, + codigo_modalidade, + modalidade, + CAST(unidade_compra AS INT) AS unidade_compra, + licitacao_numero, + sistema_origem_licitacao, + + -- Tratar valores nulos ou inválidos nas colunas de data + CASE + WHEN data_assinatura IS NULL THEN NULL + WHEN data_assinatura IS NOT NULL AND data_assinatura::text ~ '^\d{4}-\d{2}-\d{2}$' THEN TO_DATE(data_assinatura::text, 'YYYY-MM-DD') + ELSE NULL -- Retorna NULL se não for uma data válida + END AS data_assinatura, + + CASE + WHEN data_publicacao IS NULL THEN NULL + WHEN data_publicacao IS NOT NULL AND data_publicacao::text ~ '^\d{4}-\d{2}-\d{2}$' THEN TO_DATE(data_publicacao::text, 'YYYY-MM-DD') + ELSE NULL -- Retorna NULL se não for uma data válida + END AS data_publicacao, + + CASE + WHEN data_proposta_comercial IS NULL THEN NULL + WHEN data_proposta_comercial IS NOT NULL AND data_proposta_comercial::text ~ '^\d{4}-\d{2}-\d{2}$' THEN TO_DATE(data_proposta_comercial::text, 'YYYY-MM-DD') + ELSE NULL -- Retorna NULL se não for uma data válida + END AS data_proposta_comercial, + + CASE + WHEN vigencia_inicio IS NULL THEN NULL + WHEN vigencia_inicio IS NOT NULL AND vigencia_inicio::text ~ '^\d{4}-\d{2}-\d{2}$' THEN TO_DATE(vigencia_inicio::text, 'YYYY-MM-DD') + ELSE NULL -- Retorna NULL se não for uma data válida + END AS vigencia_inicio, + + CASE + WHEN vigencia_fim IS NULL THEN NULL + WHEN vigencia_fim IS NOT NULL AND vigencia_fim::text ~ '^\d{4}-\d{2}-\d{2}$' THEN TO_DATE(vigencia_fim::text, 'YYYY-MM-DD') + ELSE NULL -- Retorna NULL se não for uma data válida + END AS vigencia_fim, + + -- Conversão de valores numéricos para FLOAT ou INT + REPLACE(REPLACE(valor_inicial::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS valor_inicial, + REPLACE(REPLACE(valor_global::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS valor_global, + CAST(num_parcelas AS INT) AS num_parcelas, + REPLACE(REPLACE(valor_parcela::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS valor_parcela, + REPLACE(REPLACE(valor_acumulado::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS valor_acumulado, + + FROM raw.contratos +) + +SELECT * FROM contratos_raw diff --git a/airflow/dags/dbt/ipea/models/contratos/bronze/empenhos.sql b/airflow/dags/dbt/ipea/models/contratos/bronze/empenhos.sql new file mode 100644 index 00000000..ace9d56b --- /dev/null +++ b/airflow/dags/dbt/ipea/models/contratos/bronze/empenhos.sql @@ -0,0 +1,38 @@ +WITH empenhos AS ( + SELECT + id::TEXT AS id, + contrato_id::TEXT AS contrato_id, + unidade_gestora, + gestao, + numero AS nota_empenho, + credor, + fonte_recurso, + programa_trabalho, + planointerno, + naturezadespesa, + informacao_complementar, + sistema_origem, + links_documento_pagamento, + credor_obj_tipo, + credor_obj_cnpj_cpf_idgener, + credor_obj_nome, + + -- Tratar valores nulos ou inválidos nas colunas de data + CASE + WHEN data_emissao IS NOT NULL AND data_emissao::text ~ '^\d{4}-\d{2}-\d{2}$' THEN TO_DATE(data_emissao::text, 'YYYY-MM-DD') + ELSE NULL -- Retorna NULL se não for uma data válida + END AS data_emissao, + + -- Conversão de valores numéricos para FLOAT + REPLACE(REPLACE(empenhado::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS empenhado, + REPLACE(REPLACE(aliquidar::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS aliquidar, + REPLACE(REPLACE(liquidado::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS liquidado, + REPLACE(REPLACE(pago::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS pago, + REPLACE(REPLACE(rpinscrito::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS rpinscrito, + REPLACE(REPLACE(rpaliquidar::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS rpaliquidar, + REPLACE(REPLACE(rpliquidado::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS rpliquidado, + REPLACE(REPLACE(rppago::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS rppago + + FROM raw.empenhos +) +SELECT * FROM empenhos \ No newline at end of file diff --git a/airflow/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql b/airflow/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql new file mode 100644 index 00000000..98214e2d --- /dev/null +++ b/airflow/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql @@ -0,0 +1,36 @@ +create or replace function parse_number(in_text text) returns numeric as $$ + select + case when in_text like '(%' then regexp_replace(replace(coalesce(in_text, '0'), '.', ''), '(\()?(\d+),(\d+)(\))?', '-\2.\3')::numeric(15,2) + else replace(replace(coalesce(in_text, '0'), '.', ''), ',', '.')::numeric(15,2) end as result +$$ language sql; + +WITH + +empenhos_raw AS ( + SELECT + id::INTEGER AS id, + ne_ccor::TEXT AS ne_ccor, + ne_informacao_complementar::TEXT AS ne_informacao_complementar, + REGEXP_REPLACE(ne_num_processo, '[./-]', '') AS ne_num_processo, + ne_ccor_descricao::TEXT AS ne_ccor_descricao, + doc_observacao::TEXT AS doc_observacao, + natureza_despesa::INTEGER AS natureza_despesa, + natureza_despesa_1::TEXT AS natureza_despesa_1, + natureza_despesa_detalhada::INTEGER AS natureza_despesa_detalhada, + natureza_despesa_detalhada_1::TEXT AS natureza_despesa_detalhada_1, + ne_ccor_favorecido::TEXT AS ne_ccor_favorecido, + ne_ccor_favorecido_1::TEXT AS ne_ccor_favorecido_1, + ne_ccor_ano_emissao::INTEGER AS ne_ccor_ano_emissao, + item_informacao::INTEGER AS ne_ccor_ano_emissao_1, + -- Aplicando NULLIF e removendo parênteses antes de converter para NUMERIC + parse_number(despesas_empenhadas_controle_empenho_saldo_moeda_origem) AS despesas_empenhadas_saldo, + parse_number(despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem) AS despesas_empenhadas_movim_liquido, + parse_number(despesas_liquidadas_controle_empenho_saldo_moeda_origem) AS despesas_liquidadas_saldo, + parse_number(despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem) AS despesas_liquidadas_movim_liquido, + parse_number(despesas_pagas_controle_empenho_saldo_moeda_origem) AS despesas_pagas_saldo, + parse_number(despesas_pagas_controle_empenho_movim_liquido_moeda_origem) AS despesas_pagas_movim_liquido + FROM raw.empenhos_tesouro + WHERE ne_ccor != 'Total' +) + +SELECT * FROM empenhos_raw \ No newline at end of file diff --git a/airflow/dags/dbt/ipea/models/contratos/bronze/estagios.sql b/airflow/dags/dbt/ipea/models/contratos/bronze/estagios.sql new file mode 100644 index 00000000..8defa971 --- /dev/null +++ b/airflow/dags/dbt/ipea/models/contratos/bronze/estagios.sql @@ -0,0 +1,74 @@ +create or replace function parse_number(in_text text) returns numeric as $$ + select + case when in_text like '(%' then regexp_replace(replace(coalesce(in_text, '0'), '.', ''), '(\()?(\d+),(\d+)(\))?', '-\2.\3')::numeric(15,2) + else replace(replace(coalesce(in_text, '0'), '.', ''), ',', '.')::numeric(15,2) end as result +$$ language sql; + +-- Comentário: O erro indica que há valores com parênteses "(660000.00)" que não podem ser convertidos para double precision +-- O erro está ocorrendo nas colunas de valores monetários, especificamente em: +-- - despesas_empenhadas_controle_empenho_saldo_moeda_origem +-- - despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem +-- - despesas_liquidadas_controle_empenho_saldo_moeda_origem +-- - despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem +-- - despesas_pagas_controle_empenho_saldo_moeda_origem +-- - despesas_pagas_controle_empenho_movim_liquido_moeda_origem + +-- Precisamos modificar o CAST dessas colunas para tratar valores entre parênteses como números negativos + + + +WITH estagios_raw AS ( + SELECT + id::INTEGER as id, + ne_ccor, + ne_informacao_complementar :: TEXT, + + -- Remove o "0" inicial e pontos, barras e hífens do número do processo + CASE + WHEN LENGTH(ne_num_processo::text) > 3 THEN REGEXP_REPLACE(LTRIM(ne_num_processo::text, '0'), '[\./-]', '', 'g') + ELSE ne_num_processo::text + END AS ne_num_processo, + + ne_ccor_descricao :: TEXT, + doc_observacao :: TEXT, + + CASE + WHEN natureza_despesa::text ~ '^\d+$' THEN CAST(natureza_despesa AS INTEGER) + ELSE NULL + END AS natureza_despesa, + + natureza_despesa_1, + + CASE + WHEN natureza_despesa_detalhada::text ~ '^\d+$' THEN CAST(natureza_despesa_detalhada AS INTEGER) + ELSE NULL + END AS natureza_despesa_detalhada, + + natureza_despesa_detalhada_1, + ne_ccor_favorecido, + ne_ccor_favorecido_1, + + CASE + WHEN ano_lancamento::text ~ '^\d+$' THEN CAST(ano_lancamento AS INTEGER) + ELSE NULL + END AS ano_lancamento, + + ne_ccor_mes_emissao, + + CASE + WHEN ne_ccor_ano_emissao::text ~ '^\d+$' THEN CAST(ne_ccor_ano_emissao AS INTEGER) + ELSE NULL + END AS ne_ccor_ano_emissao, + + mes_lancamento, + parse_number(despesas_empenhadas_controle_empenho_saldo_moeda_origem) AS despesas_empenhadas_controle_empenho_saldo_moeda_origem, + parse_number(despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem) AS despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem, + parse_number(despesas_liquidadas_controle_empenho_saldo_moeda_origem) AS despesas_liquidadas_controle_empenho_saldo_moeda_origem, + parse_number(despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem) AS despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem, + parse_number(despesas_pagas_controle_empenho_saldo_moeda_origem) AS despesas_pagas_controle_empenho_saldo_moeda_origem, + parse_number(despesas_pagas_controle_empenho_movim_liquido_moeda_origem) AS despesas_pagas_controle_empenho_movim_liquido_moeda_origem + + FROM raw.estagios +) + +SELECT * FROM estagios_raw diff --git a/airflow/dags/dbt/ipea/models/contratos/bronze/faturas.sql b/airflow/dags/dbt/ipea/models/contratos/bronze/faturas.sql new file mode 100644 index 00000000..1fdccc08 --- /dev/null +++ b/airflow/dags/dbt/ipea/models/contratos/bronze/faturas.sql @@ -0,0 +1,49 @@ + + +WITH faturas_raw AS ( + SELECT + id::INTEGER AS id, + contrato_id::INTEGER AS contrato_id, + tipolistafatura_id::TEXT AS tipolistafatura_id, + justificativafatura_id::TEXT AS justificativafatura_id, + sfadrao_id::TEXT AS sfadrao_id, + numero::TEXT AS numero, + emissao::DATE AS emissao, + prazo::DATE AS prazo, + vencimento::DATE AS vencimento, + -- Limpar o formato numérico das colunas que têm problemas + REPLACE(REPLACE(valor::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS valor, + REPLACE(REPLACE(juros::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS juros, + REPLACE(REPLACE(multa::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS multa, + REPLACE(REPLACE(glosa::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS glosa, + REPLACE(REPLACE(valorliquido::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS valorliquido, + processo::TEXT AS processo, + protocolo::DATE AS protocolo, + ateste::DATE AS ateste, + repactuacao::TEXT AS repactuacao, + infcomplementar::TEXT AS infcomplementar, + mesref::INTEGER AS mesref, + anoref::INTEGER AS anoref, + situacao::TEXT AS situacao, + chave_nfe::TEXT AS chave_nfe, + jsonb_array_elements(dados_empenho::jsonb) AS dados_empenho, + dados_referencia::TEXT AS dados_referencia, + dados_item_faturado::TEXT AS dados_item_faturado + FROM raw.faturas +), + +-- Extrai os campos do JSON e transforma em colunas individuais +faturas_dados_empenho AS ( + SELECT + f.*, + dados_empenho->>'id_empenho' AS id_empenho, + upper(dados_empenho->>'numero_empenho') AS numero_empenho, + dados_empenho->>'valor_empenho' AS valor_empenho, + dados_empenho->>'subelemento' AS subelemento + FROM faturas_raw AS f +) + +-- + +SELECT * +FROM faturas_dados_empenho diff --git a/airflow/dags/dbt/ipea/models/contratos/gold/contratos_resumo.sql b/airflow/dags/dbt/ipea/models/contratos/gold/contratos_resumo.sql new file mode 100644 index 00000000..5f90eaaa --- /dev/null +++ b/airflow/dags/dbt/ipea/models/contratos/gold/contratos_resumo.sql @@ -0,0 +1,35 @@ +WITH + +valores_pagos_contratos AS ( + SELECT contrato_id AS id, + SUM(despesas_pagas_movim_liquido) AS despesas_pagas + -- FROM public_silver.silver_contratos_empenhos + FROM {{ ref('contratos_empenhos')}} + WHERE contrato_id IS NOT NULL + GROUP BY contrato_id), + + +contratos_gold AS ( + SELECT *, + CASE + WHEN vp.despesas_pagas = c.valor_global THEN 'Sim' + ELSE 'Não' + END AS pendente_baixa + -- FROM public_raw.contratos c + FROM {{ ref('contratos')}} + LEFT JOIN valores_pagos_contratos vp USING(id)) -- + +SELECT + id AS contrato_id, + numero AS numero, + modalidade AS modalidade, + situacao AS situacao, + pendente_baixa AS pendente_baixa, + CONCAT(contratante_orgao_origem_unidade_gestora_origem_codigo, ' - ', contratante_orgao_origem_unidade_gestora_origem_nome_resumido) AS "Unidade", + fornecedor_nome AS fornecedor_nome, + objeto AS objeto, + valor_global AS valor_global, + despesas_pagas AS despesas_pagas, + vigencia_inicio AS vigencia_inicio, + vigencia_fim AS vigencia_fim +FROM contratos_gold \ No newline at end of file diff --git a/airflow/dags/dbt/ipea/models/contratos/silver/contratos_empenhos.sql b/airflow/dags/dbt/ipea/models/contratos/silver/contratos_empenhos.sql new file mode 100644 index 00000000..807a22e4 --- /dev/null +++ b/airflow/dags/dbt/ipea/models/contratos/silver/contratos_empenhos.sql @@ -0,0 +1,156 @@ +WITH contratos_unicos AS ( + -- Seleciona apenas os registros onde o fornecedor_cnpj_cpf_idgener é único + SELECT * + FROM public_raw.contratos + WHERE fornecedor_cnpj_cpf_idgener IN + (SELECT fornecedor_cnpj_cpf_idgener + FROM public_raw.contratos + GROUP BY fornecedor_cnpj_cpf_idgener + HAVING COUNT(fornecedor_cnpj_cpf_idgener) = 1) +), + +processos_unicos AS ( + -- Seleciona apenas os registros onde o processo é único e não está em contratos_unicos + SELECT * + FROM public_raw.contratos + WHERE processo IN + (SELECT processo + FROM public_raw.contratos + GROUP BY processo + HAVING COUNT(processo) = 1) +), + +numeros_unicos AS ( + -- Seleciona apenas os registros onde o numero é único e não está em contratos_unicos ou processos_unicos + SELECT * + FROM public_raw.contratos + WHERE numero IN + (SELECT numero + FROM public_raw.contratos + GROUP BY numero + HAVING COUNT(numero) = 1) +), + +faturas_contratos AS ( + -- Seleciona uma única correspondência de cada fatura + SELECT DISTINCT ON (f.contrato_id) + f.numero_empenho, + f.contrato_id, + c.* + FROM public_raw.faturas f + JOIN public_raw.contratos c ON f.contrato_id = c.id +), + +empenhos_contratos AS ( + -- Para cada empenho, seleciona apenas uma correspondência de contrato com prioridade na ordem definida + SELECT + ec.*, + COALESCE(c1.id, c2.id, c3.id, f.contrato_id, c_modalidade.id) AS contrato_id, + COALESCE(c1.receita_despesa, c2.receita_despesa, c3.receita_despesa, f.receita_despesa, c_modalidade.receita_despesa) AS receita_despesa, + COALESCE(c1.numero, c2.numero, c3.numero, f.numero, c_modalidade.numero) AS numero, + COALESCE(c1.contratante_orgao_origem_codigo, c2.contratante_orgao_origem_codigo, c3.contratante_orgao_origem_codigo, f.contratante_orgao_origem_codigo, c_modalidade.contratante_orgao_origem_codigo) AS contratante_orgao_origem_codigo, + COALESCE(c1.contratante_orgao_origem_nome, c2.contratante_orgao_origem_nome, c3.contratante_orgao_origem_nome, f.contratante_orgao_origem_nome, c_modalidade.contratante_orgao_origem_nome) AS contratante_orgao_origem_nome, + COALESCE(c1.contratante_orgao_origem_unidade_gestora_origem_codigo, c2.contratante_orgao_origem_unidade_gestora_origem_codigo, c3.contratante_orgao_origem_unidade_gestora_origem_codigo, f.contratante_orgao_origem_unidade_gestora_origem_codigo, c_modalidade.contratante_orgao_origem_unidade_gestora_origem_codigo) AS contratante_orgao_origem_unidade_gestora_origem_codigo, + COALESCE(c1.contratante_orgao_origem_unidade_gestora_origem_nome_resumido, c2.contratante_orgao_origem_unidade_gestora_origem_nome_resumido, c3.contratante_orgao_origem_unidade_gestora_origem_nome_resumido, f.contratante_orgao_origem_unidade_gestora_origem_nome_resumido, c_modalidade.contratante_orgao_origem_unidade_gestora_origem_nome_resumido) AS contratante_orgao_origem_unidade_gestora_origem_nome_resumido, + COALESCE(c1.contratante_orgao_origem_unidade_gestora_origem_nome, c2.contratante_orgao_origem_unidade_gestora_origem_nome, c3.contratante_orgao_origem_unidade_gestora_origem_nome, f.contratante_orgao_origem_unidade_gestora_origem_nome, c_modalidade.contratante_orgao_origem_unidade_gestora_origem_nome) AS contratante_orgao_origem_unidade_gestora_origem_nome, + COALESCE(c1.contratante_orgao_origem_unidade_gestora_origem_sisg, c2.contratante_orgao_origem_unidade_gestora_origem_sisg, c3.contratante_orgao_origem_unidade_gestora_origem_sisg, f.contratante_orgao_origem_unidade_gestora_origem_sisg, c_modalidade.contratante_orgao_origem_unidade_gestora_origem_sisg) AS contratante_orgao_origem_unidade_gestora_origem_sisg, + COALESCE(c1.contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi, c2.contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi, c3.contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi, f.contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi, c_modalidade.contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi) AS contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi, + COALESCE(c1.contratante_orgao_origem_unidade_gestora_origem_utiliza_antecip, c2.contratante_orgao_origem_unidade_gestora_origem_utiliza_antecip, c3.contratante_orgao_origem_unidade_gestora_origem_utiliza_antecip, f.contratante_orgao_origem_unidade_gestora_origem_utiliza_antecip, c_modalidade.contratante_orgao_origem_unidade_gestora_origem_utiliza_antecip) AS contratante_orgao_origem_unidade_gestora_origem_utiliza_antecip, + COALESCE(c1.contratante_orgao_codigo, c2.contratante_orgao_codigo, c3.contratante_orgao_codigo, f.contratante_orgao_codigo, c_modalidade.contratante_orgao_codigo) AS contratante_orgao_codigo, + COALESCE(c1.contratante_orgao_nome, c2.contratante_orgao_nome, c3.contratante_orgao_nome, f.contratante_orgao_nome, c_modalidade.contratante_orgao_nome) AS contratante_orgao_nome, + COALESCE(c1.contratante_orgao_unidade_gestora_codigo, c2.contratante_orgao_unidade_gestora_codigo, c3.contratante_orgao_unidade_gestora_codigo, f.contratante_orgao_unidade_gestora_codigo, c_modalidade.contratante_orgao_unidade_gestora_codigo) AS contratante_orgao_unidade_gestora_codigo, + COALESCE(c1.contratante_orgao_unidade_gestora_nome_resumido, c2.contratante_orgao_unidade_gestora_nome_resumido, c3.contratante_orgao_unidade_gestora_nome_resumido, f.contratante_orgao_unidade_gestora_nome_resumido, c_modalidade.contratante_orgao_unidade_gestora_nome_resumido) AS contratante_orgao_unidade_gestora_nome_resumido, + COALESCE(c1.contratante_orgao_unidade_gestora_nome, c2.contratante_orgao_unidade_gestora_nome, c3.contratante_orgao_unidade_gestora_nome, f.contratante_orgao_unidade_gestora_nome, c_modalidade.contratante_orgao_unidade_gestora_nome) AS contratante_orgao_unidade_gestora_nome, + COALESCE(c1.contratante_orgao_unidade_gestora_sisg, c2.contratante_orgao_unidade_gestora_sisg, c3.contratante_orgao_unidade_gestora_sisg, f.contratante_orgao_unidade_gestora_sisg, c_modalidade.contratante_orgao_unidade_gestora_sisg) AS contratante_orgao_unidade_gestora_sisg, + COALESCE(c1.contratante_orgao_unidade_gestora_utiliza_siafi, c2.contratante_orgao_unidade_gestora_utiliza_siafi, c3.contratante_orgao_unidade_gestora_utiliza_siafi, f.contratante_orgao_unidade_gestora_utiliza_siafi, c_modalidade.contratante_orgao_unidade_gestora_utiliza_siafi) AS contratante_orgao_unidade_gestora_utiliza_siafi, + COALESCE(c1.contratante_orgao_unidade_gestora_utiliza_antecipagov, c2.contratante_orgao_unidade_gestora_utiliza_antecipagov, c3.contratante_orgao_unidade_gestora_utiliza_antecipagov, f.contratante_orgao_unidade_gestora_utiliza_antecipagov, c_modalidade.contratante_orgao_unidade_gestora_utiliza_antecipagov, f.contratante_orgao_unidade_gestora_utiliza_antecipagov) AS contratante_orgao_unidade_gestora_utiliza_antecipagov, + COALESCE(c1.fornecedor_tipo, c2.fornecedor_tipo, c3.fornecedor_tipo, f.fornecedor_tipo, c_modalidade.fornecedor_tipo) AS fornecedor_tipo, + COALESCE(c1.fornecedor_cnpj_cpf_idgener, c2.fornecedor_cnpj_cpf_idgener, c3.fornecedor_cnpj_cpf_idgener, f.fornecedor_cnpj_cpf_idgener, c_modalidade.fornecedor_cnpj_cpf_idgener) AS fornecedor_cnpj_cpf_idgener, + COALESCE(c1.fornecedor_nome, c2.fornecedor_nome, c3.fornecedor_nome, f.fornecedor_nome, c_modalidade.fornecedor_nome) AS fornecedor_nome, + COALESCE(c1.codigo_tipo, c2.codigo_tipo, c3.codigo_tipo, f.codigo_tipo, c_modalidade.codigo_tipo) AS codigo_tipo, + COALESCE(c1.tipo, c2.tipo, c3.tipo, f.tipo, c_modalidade.tipo) AS tipo, + COALESCE(c1.subtipo, c2.subtipo, c3.subtipo, f.subtipo, c_modalidade.subtipo) AS subtipo, + COALESCE(c1.prorrogavel, c2.prorrogavel, c3.prorrogavel, f.prorrogavel, c_modalidade.prorrogavel) AS prorrogavel, + COALESCE(c1.situacao, c2.situacao, c3.situacao, f.situacao, c_modalidade.situacao) AS situacao, + COALESCE(c1.justificativa_inativo, c2.justificativa_inativo, c3.justificativa_inativo, f.justificativa_inativo, c_modalidade.justificativa_inativo) AS justificativa_inativo, + COALESCE(c1.categoria, c2.categoria, c3.categoria, f.categoria, c_modalidade.categoria) AS categoria, + COALESCE(c1.subcategoria, c2.subcategoria, c3.subcategoria, f.subcategoria, c_modalidade.subcategoria) AS subcategoria, + COALESCE(c1.unidades_requisitantes, c2.unidades_requisitantes, c3.unidades_requisitantes, f.unidades_requisitantes, c_modalidade.unidades_requisitantes) AS unidades_requisitantes, + COALESCE(c1.processo, c2.processo, c3.processo, f.processo, c_modalidade.processo) AS processo, + COALESCE(c1.objeto, c2.objeto, c3.objeto, f.objeto, c_modalidade.objeto) AS objeto, + COALESCE(c1.amparo_legal, c2.amparo_legal, c3.amparo_legal, f.amparo_legal, c_modalidade.amparo_legal) AS amparo_legal, + COALESCE(c1.informacao_complementar, c2.informacao_complementar, c3.informacao_complementar, f.informacao_complementar, c_modalidade.informacao_complementar) AS informacao_complementar, + COALESCE(c1.codigo_modalidade, c2.codigo_modalidade, c3.codigo_modalidade, f.codigo_modalidade, c_modalidade.codigo_modalidade) AS codigo_modalidade, + COALESCE(c1.modalidade, c2.modalidade, c3.modalidade, f.modalidade, c_modalidade.modalidade) AS modalidade, + COALESCE(c1.unidade_compra, c2.unidade_compra, c3.unidade_compra, f.unidade_compra, c_modalidade.unidade_compra) AS unidade_compra, + COALESCE(c1.licitacao_numero, c2.licitacao_numero, c3.licitacao_numero, f.licitacao_numero, c_modalidade.licitacao_numero) AS licitacao_numero, + COALESCE(c1.sistema_origem_licitacao, c2.sistema_origem_licitacao, c3.sistema_origem_licitacao, f.sistema_origem_licitacao, c_modalidade.sistema_origem_licitacao) AS sistema_origem_licitacao, + COALESCE(c1.data_assinatura, c2.data_assinatura, c3.data_assinatura, f.data_assinatura, c_modalidade.data_assinatura) AS data_assinatura, + COALESCE(c1.data_publicacao, c2.data_publicacao, c3.data_publicacao, f.data_publicacao, c_modalidade.data_publicacao) AS data_publicacao, + COALESCE(c1.data_proposta_comercial, c2.data_proposta_comercial, c3.data_proposta_comercial, f.data_proposta_comercial, c_modalidade.data_proposta_comercial) AS data_proposta_comercial, + COALESCE(c1.vigencia_inicio, c2.vigencia_inicio, c3.vigencia_inicio, f.vigencia_inicio, c_modalidade.vigencia_inicio) AS vigencia_inicio, + COALESCE(c1.vigencia_fim, c2.vigencia_fim, c3.vigencia_fim, f.vigencia_fim, c_modalidade.vigencia_fim) AS vigencia_fim, + COALESCE(c1.valor_inicial, c2.valor_inicial, c3.valor_inicial, f.valor_inicial, c_modalidade.valor_inicial) AS valor_inicial, + COALESCE(c1.valor_global, c2.valor_global, c3.valor_global, f.valor_global, c_modalidade.valor_global) AS valor_global, + COALESCE(c1.num_parcelas, c2.num_parcelas, c3.num_parcelas, f.num_parcelas, c_modalidade.num_parcelas) AS num_parcelas, + COALESCE(c1.valor_parcela, c2.valor_parcela, c3.valor_parcela, f.valor_parcela, c_modalidade.valor_parcela) AS valor_parcela, + COALESCE(c1.valor_acumulado, c2.valor_acumulado, c3.valor_acumulado, f.valor_acumulado, c_modalidade.valor_acumulado) AS valor_acumulado, + COALESCE(c1.links_historico, c2.links_historico, c3.links_historico, f.links_historico, c_modalidade.links_historico) AS links_historico, + COALESCE(c1.links_empenhos, c2.links_empenhos, c3.links_empenhos, f.links_empenhos, c_modalidade.links_empenhos) AS links_empenhos, + COALESCE(c1.links_cronograma, c2.links_cronograma, c3.links_cronograma, f.links_cronograma, c_modalidade.links_cronograma) AS links_cronograma, + COALESCE(c1.links_garantias, c2.links_garantias, c3.links_garantias, f.links_garantias, c_modalidade.links_garantias) AS links_garantias, + COALESCE(c1.links_itens, c2.links_itens, c3.links_itens, f.links_itens, c_modalidade.links_itens) AS links_itens, + COALESCE(c1.links_prepostos, c2.links_prepostos, c3.links_prepostos, f.links_prepostos, c_modalidade.links_prepostos) AS links_prepostos, + COALESCE(c1.links_responsaveis, c2.links_responsaveis, c3.links_responsaveis, f.links_responsaveis, c_modalidade.links_responsaveis) AS links_responsaveis, + COALESCE(c1.links_despesas_acessorias, c2.links_despesas_acessorias, c3.links_despesas_acessorias, f.links_despesas_acessorias, c_modalidade.links_despesas_acessorias) AS links_despesas_acessorias, + COALESCE(c1.links_faturas, c2.links_faturas, c3.links_faturas, f.links_faturas, c_modalidade.links_faturas) AS links_faturas, + COALESCE(c1.links_ocorrencias, c2.links_ocorrencias, c3.links_ocorrencias, f.links_ocorrencias, c_modalidade.links_ocorrencias) AS links_ocorrencias, + COALESCE(c1.links_terceirizados, c2.links_terceirizados, c3.links_terceirizados, f.links_terceirizados, c_modalidade.links_terceirizados) AS links_terceirizados, + COALESCE(c1.links_arquivos, c2.links_arquivos, c3.links_arquivos, f.links_arquivos, c_modalidade.links_arquivos) AS links_arquivos, + + CASE + WHEN ec.ne_ccor_favorecido = c1.fornecedor_cnpj_cpf_idgener THEN 'fornecedor_cnpj_cpf_idgener' + WHEN ec.ne_ccor_favorecido != c1.fornecedor_cnpj_cpf_idgener + AND REGEXP_REPLACE(ec.ne_num_processo, '[\./-]', '', 'g') = c2.processo THEN 'processo' + WHEN ec.ne_ccor_favorecido != c1.fornecedor_cnpj_cpf_idgener + AND REGEXP_REPLACE(ec.ne_num_processo, '[\./-]', '', 'g') != c2.processo + AND RIGHT(ec.ne_ccor, 12) = c3.numero THEN 'numero' + WHEN ec.ne_ccor_favorecido != c1.fornecedor_cnpj_cpf_idgener + AND REGEXP_REPLACE(ec.ne_num_processo, '[\./-]', '', 'g') != c2.processo + AND RIGHT(ec.ne_ccor, 12) != c3.numero + AND RIGHT(ec.ne_ccor, 12) = f.numero_empenho THEN 'faturas' + WHEN ec.ne_ccor_favorecido != c1.fornecedor_cnpj_cpf_idgener + AND REGEXP_REPLACE(ec.ne_num_processo, '[\./-]', '', 'g') != c2.processo + AND RIGHT(ec.ne_ccor, 12) != c3.numero + AND RIGHT(ec.ne_ccor, 12) != f.numero_empenho + AND ( + (c_modalidade.codigo_modalidade = 5 + AND ec.ne_informacao_complementar LIKE CONCAT('%', c_modalidade.contratante_orgao_unidade_gestora_codigo, TO_CHAR(c_modalidade.codigo_modalidade, 'FM00'), REGEXP_REPLACE(c_modalidade.numero, '[\./-]', '', 'g'), '%')) + OR + (c_modalidade.codigo_modalidade = 7 + AND ec.ne_informacao_complementar LIKE CONCAT('%', c_modalidade.contratante_orgao_unidade_gestora_codigo, TO_CHAR(c_modalidade.codigo_modalidade, 'FM00'), REGEXP_REPLACE(c_modalidade.licitacao_numero, '[\./-]', '', 'g'), '%')) + ) + THEN 'informacao_complementar' + ELSE NULL + END AS origem + + FROM {{ ref('empenhos_tesouro') }} ec + LEFT JOIN contratos_unicos c1 ON ec.ne_ccor_favorecido = c1.fornecedor_cnpj_cpf_idgener + LEFT JOIN processos_unicos c2 ON REGEXP_REPLACE(ec.ne_num_processo, '[\./-]', '', 'g') = c2.processo AND c1.id IS NULL + LEFT JOIN numeros_unicos c3 ON RIGHT(ec.ne_ccor, 12) = c3.numero AND c1.id IS NULL AND c2.id IS NULL + LEFT JOIN faturas_contratos f ON RIGHT(ec.ne_ccor,12) = f.numero_empenho AND c1.id IS NULL AND c2.id IS NULL AND c3.id IS NULL + LEFT JOIN public_raw.contratos c_modalidade ON + ( + ( + c_modalidade.codigo_modalidade = '5' + AND ec.ne_informacao_complementar like CONCAT('%',c_modalidade.contratante_orgao_unidade_gestora_codigo, TO_CHAR(c_modalidade.codigo_modalidade, 'FM00'), REGEXP_REPLACE(c_modalidade.numero, '[\./-]', '', 'g'), '%') + ) + OR + ( + c_modalidade.codigo_modalidade = '7' + AND ec.ne_informacao_complementar like CONCAT('%',c_modalidade.contratante_orgao_unidade_gestora_codigo, TO_CHAR(c_modalidade.codigo_modalidade, 'FM00'), REGEXP_REPLACE(c_modalidade.licitacao_numero, '[\./-]', '', 'g'), '%') + ) + ) AND c1.id IS NULL AND c2.id IS NULL AND c3.id IS NULL AND f.id IS NULL +) + +SELECT * FROM empenhos_contratos \ No newline at end of file diff --git a/airflow/dags/dbt/ipea/profiles.yml b/airflow/dags/dbt/ipea/profiles.yml new file mode 100644 index 00000000..5aceadf1 --- /dev/null +++ b/airflow/dags/dbt/ipea/profiles.yml @@ -0,0 +1,11 @@ +ipea: + target: prod + outputs: + prod: + type: postgres + host: 10.0.0.73 + user: analytics + password: xQ3hxNJThsVx3WqEp6Yr0hhtptSMbmbFWyL2 + port: 5432 + dbname: analytics + schema: ipea From 0352e6f995ef72773f4643774bafb09d738a1cb5 Mon Sep 17 00:00:00 2001 From: arthrok Date: Thu, 30 Jan 2025 14:37:08 -0300 Subject: [PATCH 003/317] fix: removendo arquivo de init da pasta airflow --- airflow/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 airflow/__init__.py diff --git a/airflow/__init__.py b/airflow/__init__.py deleted file mode 100644 index e69de29b..00000000 From 17f3df2bf81b3954d02eed5d5ff757bf97e974be Mon Sep 17 00:00:00 2001 From: Arthur Melo Date: Tue, 4 Feb 2025 18:54:45 +0000 Subject: [PATCH 004/317] Fix/ci --- .env | 28 ---------------------------- .gitlab-ci.yml | 2 ++ airflow/plugins/cliente_base.py | 2 ++ airflow/plugins/cliente_contratos.py | 2 +- airflow/plugins/cliente_estrutura.py | 2 +- airflow/plugins/cliente_siafi.py | 2 +- docker-compose.yml | 6 ++++-- mypy.ini | 2 ++ 8 files changed, 13 insertions(+), 33 deletions(-) delete mode 100755 .env create mode 100644 mypy.ini diff --git a/.env b/.env deleted file mode 100755 index d1da9b84..00000000 --- a/.env +++ /dev/null @@ -1,28 +0,0 @@ -# <---------- Airflow ----------> - -AIRFLOW_IMAGE_NAME=apache/airflow:latest -AIRFLOW__CORE__FERNET_KEY='lmnHJcz5u4D8SPEeD4qwAf1TUk_yLXXnQfwlvQ8MXsU=' -AIRFLOW_HOME=/opt/airflow/ -_AIRFLOW_WWW_USER_USERNAME=airflow -_AIRFLOW_WWW_USER_PASSWORD=airflow -AIRFLOW_UID=50000 - - -# <---------- Postgres Airflow ----------> - -POSTGRES_USER=postgres -POSTGRES_PASSWORD=postgres -POSTGRES_DB=postgres - - -# <---------- Postgres Data Warehouse ----------> -POSTGRES_USER_DW=postgres_dw -POSTGRES_PASSWORD_DW=postgres_dw -POSTGRES_DB_DW=data_warehouse - - -# <---------- MinIO ----------> -MINIO_ENDPOINT=minio:9000 -MINIO_ACCESS_KEY=minioadmin -MINIO_SECRET_KEY=minioadmin -MINIO_BUCKET=data-lake-ipea diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ccfd75c0..7993d7ec 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,8 @@ image: python:3.11-slim variables: + PYTHONPATH: "${CI_PROJECT_DIR}/dags:${CI_PROJECT_DIR}/plugins:./airflow/dags:./airflow/plugins" + MYPYPATH: "${CI_PROJECT_DIR}/plugins" POETRY_HOME: "/opt/poetry" POETRY_VERSION: "1.8.5" POETRY_VIRTUALENVS_IN_PROJECT: "true" diff --git a/airflow/plugins/cliente_base.py b/airflow/plugins/cliente_base.py index 76afe905..6d70a1ac 100644 --- a/airflow/plugins/cliente_base.py +++ b/airflow/plugins/cliente_base.py @@ -1,3 +1,5 @@ +# mypy: ignore-errors + import http import logging import time diff --git a/airflow/plugins/cliente_contratos.py b/airflow/plugins/cliente_contratos.py index 5f01a619..c36b5ee2 100644 --- a/airflow/plugins/cliente_contratos.py +++ b/airflow/plugins/cliente_contratos.py @@ -1,6 +1,6 @@ import http -from .cliente_base import ClienteBase +from cliente_base import ClienteBase class ClienteContratos(ClienteBase): diff --git a/airflow/plugins/cliente_estrutura.py b/airflow/plugins/cliente_estrutura.py index f0f19b67..7522b657 100644 --- a/airflow/plugins/cliente_estrutura.py +++ b/airflow/plugins/cliente_estrutura.py @@ -1,7 +1,7 @@ import http from typing import Optional -from .cliente_base import ClienteBase +from cliente_base import ClienteBase class ClienteEstrutura(ClienteBase): diff --git a/airflow/plugins/cliente_siafi.py b/airflow/plugins/cliente_siafi.py index 15129b13..108f0449 100644 --- a/airflow/plugins/cliente_siafi.py +++ b/airflow/plugins/cliente_siafi.py @@ -4,7 +4,7 @@ import requests from httpx import HTTPStatusError -from .cliente_base import ClienteBase +from cliente_base import ClienteBase class ClienteSiafi(ClienteBase): diff --git a/docker-compose.yml b/docker-compose.yml index 45e14fb2..5c6e4425 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,8 +7,8 @@ x-airflow-common: &airflow-common env_file: - .env volumes: - - ./dags:${AIRFLOW_HOME}/dags/ - - ./plugins:${AIRFLOW_HOME}/plugins/ + - ./airflow/dags:${AIRFLOW_HOME}/dags/ + - ./airflow/plugins:${AIRFLOW_HOME}/plugins/ depends_on: &airflow-common-depends-on postgres: condition: service_healthy @@ -35,6 +35,8 @@ x-airflow-environment: &airflow-common-env _AIRFLOW_WWW_USER_CREATE: 'true' _AIRFLOW_WWW_USER_USERNAME: ${_AIRFLOW_WWW_USER_USERNAME:-airflow} _AIRFLOW_WWW_USER_PASSWORD: ${_AIRFLOW_WWW_USER_PASSWORD:-airflow} + AIRFLOW__CORE__PLUGINS_FOLDER: ${AIRFLOW_HOME}/plugins + AIRFLOW__CORE__DAGS_FOLDER: ${AIRFLOW_HOME}/dags services: postgres: diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 00000000..1215375e --- /dev/null +++ b/mypy.ini @@ -0,0 +1,2 @@ +[mypy] +ignore_missing_imports = True \ No newline at end of file From 664d41db082aa554d02c0a7da002ff048e6f5579 Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Tue, 4 Feb 2025 21:42:12 +0000 Subject: [PATCH 005/317] Feat/migrar dags --- .gitignore | 3 +- airflow/plugins/cliente_base.py | 53 ---- airflow/plugins/cliente_contratos.py | 76 ------ airflow/plugins/cliente_postgres.py | 145 ----------- .../plugins => airflow_lappis}/__init__.py | 0 airflow_lappis/dags/__init__.py | 0 airflow_lappis/dags/data_ingest/__init__.py | 0 .../dags/data_ingest/contratos_ingest_dag.py | 56 +++++ .../dags/data_ingest/cronograma_ingest_dag.py | 57 +++++ .../dags/data_ingest/empenhos_ingest_dag.py | 63 +++++ .../empenhos_tesouro_ingest_dag.py | 123 ++++++++++ .../estagios_tesouro_ingest_dag.py | 126 ++++++++++ .../dags/data_ingest/faturas_ingest_dag.py | 58 +++++ .../unidade_organizacional_ingest_dag.py | 92 +++++++ .../dags/dbt/.user.yml | 0 .../dags/dbt/ipea/.user.yml | 0 .../dags/dbt/ipea/dbt_project.yml | 0 .../dbt/ipea/macros/get_custom_schema.sql | 0 .../models/contratos/bronze/contratos.sql | 0 .../ipea/models/contratos/bronze/empenhos.sql | 0 .../contratos/bronze/empenhos_tesouro.sql | 0 .../ipea/models/contratos/bronze/estagios.sql | 0 .../ipea/models/contratos/bronze/faturas.sql | 0 .../contratos/gold/contratos_resumo.sql | 0 .../contratos/silver/contratos_empenhos.sql | 0 .../dags/dbt/ipea/profiles.yml | 0 airflow_lappis/helpers/__init__.py | 0 airflow_lappis/helpers/postgres_helpers.py | 17 ++ airflow_lappis/plugins/__init__.py | 0 airflow_lappis/plugins/cliente_base.py | 66 +++++ airflow_lappis/plugins/cliente_contratos.py | 133 ++++++++++ airflow_lappis/plugins/cliente_email.py | 124 ++++++++++ .../plugins/cliente_estrutura.py | 2 +- airflow_lappis/plugins/cliente_postgres.py | 228 ++++++++++++++++++ .../plugins/cliente_siafi.py | 2 +- .../plugins/cliente_siape.py | 0 jupyter/test_apis.ipynb | 18 +- mypy.ini | 2 - pyproject.toml | 2 + requirements.txt | 34 +-- 40 files changed, 1176 insertions(+), 304 deletions(-) delete mode 100644 airflow/plugins/cliente_base.py delete mode 100644 airflow/plugins/cliente_contratos.py delete mode 100644 airflow/plugins/cliente_postgres.py rename {airflow/plugins => airflow_lappis}/__init__.py (100%) create mode 100644 airflow_lappis/dags/__init__.py create mode 100644 airflow_lappis/dags/data_ingest/__init__.py create mode 100644 airflow_lappis/dags/data_ingest/contratos_ingest_dag.py create mode 100644 airflow_lappis/dags/data_ingest/cronograma_ingest_dag.py create mode 100644 airflow_lappis/dags/data_ingest/empenhos_ingest_dag.py create mode 100644 airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py create mode 100644 airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py create mode 100644 airflow_lappis/dags/data_ingest/faturas_ingest_dag.py create mode 100644 airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py rename {airflow => airflow_lappis}/dags/dbt/.user.yml (100%) rename {airflow => airflow_lappis}/dags/dbt/ipea/.user.yml (100%) rename {airflow => airflow_lappis}/dags/dbt/ipea/dbt_project.yml (100%) rename {airflow => airflow_lappis}/dags/dbt/ipea/macros/get_custom_schema.sql (100%) rename {airflow => airflow_lappis}/dags/dbt/ipea/models/contratos/bronze/contratos.sql (100%) rename {airflow => airflow_lappis}/dags/dbt/ipea/models/contratos/bronze/empenhos.sql (100%) rename {airflow => airflow_lappis}/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql (100%) rename {airflow => airflow_lappis}/dags/dbt/ipea/models/contratos/bronze/estagios.sql (100%) rename {airflow => airflow_lappis}/dags/dbt/ipea/models/contratos/bronze/faturas.sql (100%) rename {airflow => airflow_lappis}/dags/dbt/ipea/models/contratos/gold/contratos_resumo.sql (100%) rename {airflow => airflow_lappis}/dags/dbt/ipea/models/contratos/silver/contratos_empenhos.sql (100%) rename {airflow => airflow_lappis}/dags/dbt/ipea/profiles.yml (100%) create mode 100644 airflow_lappis/helpers/__init__.py create mode 100644 airflow_lappis/helpers/postgres_helpers.py create mode 100644 airflow_lappis/plugins/__init__.py create mode 100644 airflow_lappis/plugins/cliente_base.py create mode 100644 airflow_lappis/plugins/cliente_contratos.py create mode 100644 airflow_lappis/plugins/cliente_email.py rename {airflow => airflow_lappis}/plugins/cliente_estrutura.py (97%) create mode 100644 airflow_lappis/plugins/cliente_postgres.py rename {airflow => airflow_lappis}/plugins/cliente_siafi.py (98%) rename {airflow => airflow_lappis}/plugins/cliente_siape.py (100%) delete mode 100644 mypy.ini diff --git a/.gitignore b/.gitignore index 4ffde2ce..c4d5ff96 100644 --- a/.gitignore +++ b/.gitignore @@ -41,7 +41,8 @@ dbt_packages/ logs/ # Jupyter -.ipynb_checkpoints/ +**/*.ipynb_checkpoints/ +**/*.ipynb # System files .DS_Store diff --git a/airflow/plugins/cliente_base.py b/airflow/plugins/cliente_base.py deleted file mode 100644 index 6d70a1ac..00000000 --- a/airflow/plugins/cliente_base.py +++ /dev/null @@ -1,53 +0,0 @@ -# mypy: ignore-errors - -import http -import logging -import time -from http import HTTPStatus -from typing import Tuple, Optional, Any - -import httpx - - -class ClienteBase(object): - - DEFAULT_MAX_RETRIES = 5 - DEFAULT_SLEEP_SECONDS = 1 - DEFAULT_TIMEOUT = 30 - - def __init__(self, base_url: str, **kwargs: Optional[dict]) -> None: - self.base_url = base_url - self.client = httpx.Client(base_url=base_url, **kwargs) - - def request( - self, method: http.HTTPMethod, path: str, **kwargs: Any - ) -> Tuple[http.HTTPStatus, Optional[dict | list]]: - """ - Faz uma requisição HTTP em até DEFAULT_MAX_RETRIES+1 tentativas. - - Args: - method (HTTPMethod): HTTP Method. - path (str): URL path. - - Returns: - Tuple[http.HTTPStatus, dict]: status e resposta da requisição HTTP. - """ - kwargs["timeout"] = kwargs.get("timeout", self.DEFAULT_TIMEOUT) - response = None - - for attempt in range(self.DEFAULT_MAX_RETRIES): - try: - response = self.client.request(method, path, **kwargs) - response.raise_for_status() - return HTTPStatus(response.status_code), response.json() - except httpx.HTTPError as e: - if attempt < self.DEFAULT_MAX_RETRIES: - status = response.status_code if response else "Unknown" - logging.warning(f"API falhou com status {status}") - time.sleep(attempt**2 * self.DEFAULT_SLEEP_SECONDS) - else: - raise Exception( - "API falhou após o número máximo de tentativas!" - ) from e - - return HTTPStatus.INTERNAL_SERVER_ERROR, None diff --git a/airflow/plugins/cliente_contratos.py b/airflow/plugins/cliente_contratos.py deleted file mode 100644 index c36b5ee2..00000000 --- a/airflow/plugins/cliente_contratos.py +++ /dev/null @@ -1,76 +0,0 @@ -import http - -from cliente_base import ClienteBase - - -class ClienteContratos(ClienteBase): - - BASE_URL = "https://contratos.comprasnet.gov.br/api" - BASE_HEADER = {"accept": "application/json"} - - def __init__(self) -> None: - super().__init__(base_url=ClienteContratos.BASE_URL) - - def get_contratos_by_ug(self, ug_code: str) -> list | None: - """ - Obter todos os contratos ativos de uma UG específica. - - Args: - ug_code (str): UG code - - Returns: - list: lista de contratos por ug - """ - endpoint = f"/contrato/ug/{ug_code}" - status, data = self.request( - http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER - ) - return data if status == http.HTTPStatus.OK and type(data) is list else None - - def get_faturas_by_contrato_id(self, contrato_id: str) -> list | None: - """ - Obter todas as faturas de um contrato específico. - - Args: - contrato_id (str): id do contrato - - Returns: - list: as faturas de um contrato específico. - """ - endpoint = f"/contrato/{contrato_id}/faturas" - status, data = self.request( - http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER - ) - return data if status == http.HTTPStatus.OK and type(data) is list else None - - def get_empenhos_by_contrato_id(self, contrato_id: str) -> list | None: - """ - Obter todos os empenhos de um contrato específico. - - Args: - contrato_id (str): id do contrato - - Returns: - list: os empenhos de um contrato específico. - """ - endpoint = f"/contrato/{contrato_id}/empenhos" - status, data = self.request( - http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER - ) - return data if status == http.HTTPStatus.OK and type(data) is list else None - - def get_cronograma_by_contrato_id(self, contrato_id: str) -> list | None: - """ - Obter todos os cronogramas de um contrato específico. - - Args: - contrato_id (str): id do contrato - - Returns: - list: cronogramas de um contrato específico. - """ - endpoint = f"/contrato/{contrato_id}/cronograma" - status, data = self.request( - http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER - ) - return data if status == http.HTTPStatus.OK and type(data) is list else None diff --git a/airflow/plugins/cliente_postgres.py b/airflow/plugins/cliente_postgres.py deleted file mode 100644 index 10b766a0..00000000 --- a/airflow/plugins/cliente_postgres.py +++ /dev/null @@ -1,145 +0,0 @@ -from typing import Any, Dict, List, Optional, Tuple -import psycopg2 -from psycopg2.extras import execute_values -from pandas import json_normalize - - -class ClientPostgresDB: - """Client for interacting with PostgreSQL database.""" - - SEPARATOR = "_" - - TYPE_MAP = {int: "BIGINT", float: "NUMERIC", bool: "BOOLEAN"} - - @staticmethod - def _get_column_type(value: Any) -> str: - """ - Determine PostgreSQL column type from Python value. - - Args: - value: Python value to analyze - - Returns: - PostgreSQL column type as string - """ - return ClientPostgresDB.TYPE_MAP.get(type(value), "TEXT") - - @staticmethod - def _flatten_data(data: List[Dict[str, Any]]) -> List[Dict[str, Any]]: - """ - Flattens a list of deep dictionaries into a list of flat dictionaries. - - Args: - data (List[Dict[str, Any]]): List of dictionaries to flatten. - - Returns: - List[Dict[str, Any]]: List of flat dictionaries. - """ - return list( - map( - lambda d: { - str(k): v if type(v) is not list else str(v) for k, v in d.items() - }, - json_normalize(data, sep=ClientPostgresDB.SEPARATOR).to_dict( - orient="records" - ), - ) - ) - - def __init__(self, conn_str: str) -> None: - self.conn_str = conn_str - - def create_table_if_not_exists( - self, - sample_data: Dict[str, Any], - table_name: str, - primary_key: Optional[str] = None, - schema: str = "raw", - ) -> None: - """Create table dynamically based on data structure. - - Args: - sample_data: Sample data to determine schema - table_name: Name of table to create - primary_key: Primary key column name - schema: Database schema name - """ - with psycopg2.connect(self.conn_str) as conn: - with conn.cursor() as cursor: - cursor.execute(f"CREATE SCHEMA IF NOT EXISTS {schema};") - - flattened_sample = self._flatten_data([sample_data])[0] - column_definitions: List[str] = [] - - for column, value in flattened_sample.items(): - col_type = self._get_column_type(value) - column_definitions.append(f"{column} {col_type}") - - if primary_key and primary_key in flattened_sample: - column_definitions.append(f"PRIMARY KEY ({primary_key})") - - create_table_query = f""" - CREATE TABLE IF NOT EXISTS {schema}.{table_name} ( - {', '.join(column_definitions)} - );""" - - try: - cursor.execute(create_table_query) - except psycopg2.Error as err: - raise RuntimeError( - f"Failed to create table {schema}.{table_name}" - ) from err - - def insert_data( - self, - data: List[Dict[str, Any]], - table_name: str, - conflict_field: Optional[str] = None, - primary_key: Optional[str] = None, - schema: str = "raw", - ) -> None: - """Insert data into database table. - - Args: - data: List of dictionaries to insert - table_name: Target table name - conflict_field: Column name for conflict resolution - primary_key: Primary key column name - schema: Database schema name - """ - if not data: - return - - self.create_table_if_not_exists(data[0], table_name, primary_key=primary_key) - - flattened_data = self._flatten_data(data) - columns = list(flattened_data[0].keys()) - values = [tuple(item.values()) for item in flattened_data] - - sql = f""" - INSERT INTO {schema}.{table_name} ({', '.join(columns)}) - VALUES %s - """ - - if conflict_field: - sql += f" ON CONFLICT ({conflict_field}) DO NOTHING" - - with psycopg2.connect(self.conn_str) as conn: - with conn.cursor() as cursor: - try: - execute_values(cursor, sql, values) - except psycopg2.Error as err: - raise RuntimeError( - f"Failed to insert data into {table_name}" - ) from err - - def execute_query(self, query: str) -> List[Tuple[Any, ...]]: - """Get all contract IDs from contratos table. - - Returns: - List of contract IDs - """ - with psycopg2.connect(self.conn_str) as conn: - with conn.cursor() as cursor: - cursor.execute(query) - return cursor.fetchall() diff --git a/airflow/plugins/__init__.py b/airflow_lappis/__init__.py similarity index 100% rename from airflow/plugins/__init__.py rename to airflow_lappis/__init__.py diff --git a/airflow_lappis/dags/__init__.py b/airflow_lappis/dags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/airflow_lappis/dags/data_ingest/__init__.py b/airflow_lappis/dags/data_ingest/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py b/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py new file mode 100644 index 00000000..322cd79a --- /dev/null +++ b/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py @@ -0,0 +1,56 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from ...helpers.postgres_helpers import get_postgres_conn +from ...plugins.cliente_contratos import ClienteContratos +from ...plugins.cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Davi", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["contratos_api"], +) +def api_contratos_dag() -> None: + """DAG para buscar e armazenar contratos de uma API no PostgreSQL.""" + + @task + def fetch_and_store_contratos() -> None: + logging.info("[contratos_ingest_dag.py] Starting fetch_and_store_contratos task") + api = ClienteContratos() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + ug_codes = [113601, 113602] + + for ug_code in ug_codes: + logging.info( + f"[contratos_ingest_dag.py] Fetching contratos for UG code: {ug_code}" + ) + contratos = api.get_contratos_by_ug(ug_code) + if contratos: + logging.info( + f"[contratos_ingest_dag.py] Inserting contratos for UG code: " + f"{ug_code} into PostgreSQL" + ) + db.insert_data( + contratos, + "contratos", + conflict_fields=["id"], + primary_key=["id"], + schema="compras_gov", + ) + else: + logging.warning( + f"[contratos_ingest_dag.py] No contratos found for UG code: {ug_code}" + ) + + fetch_and_store_contratos() + + +dag_instance = api_contratos_dag() diff --git a/airflow_lappis/dags/data_ingest/cronograma_ingest_dag.py b/airflow_lappis/dags/data_ingest/cronograma_ingest_dag.py new file mode 100644 index 00000000..568aae13 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/cronograma_ingest_dag.py @@ -0,0 +1,57 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from ...helpers.postgres_helpers import get_postgres_conn +from ...plugins.cliente_contratos import ClienteContratos +from ...plugins.cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["cronogramas_api"], +) +def api_cronogramas_dag() -> None: + """DAG para buscar e armazenar cronogramas de uma API no PostgreSQL.""" + + @task + def fetch_cronogramas() -> None: + logging.info("[cronograma_ingest_dag.py] Starting fetch_cronogramas task") + api = ClienteContratos() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + contratos_ids = db.get_contratos_ids() + + for contrato_id in contratos_ids: + logging.info( + f"[cronograma_ingest_dag.py] Fetching cronograma for contrato ID: " + f"{contrato_id}" + ) + cronograma = api.get_cronograma_by_contrato_id(contrato_id) + if cronograma: + logging.info( + f"[cronograma_ingest_dag.py] Inserting cronograma for contrato ID: " + f"{contrato_id} into PostgreSQL" + ) + db.insert_data( + cronograma, + "cronograma", + conflict_fields=["id"], + primary_key=["id"], + schema="compras_gov", + ) + else: + logging.warning( + f"[cronograma_ingest_dag.py] No cronograma found for contrato ID: " + f"{contrato_id}" + ) + + fetch_cronogramas() + + +dag_instance = api_cronogramas_dag() diff --git a/airflow_lappis/dags/data_ingest/empenhos_ingest_dag.py b/airflow_lappis/dags/data_ingest/empenhos_ingest_dag.py new file mode 100644 index 00000000..02b3c748 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/empenhos_ingest_dag.py @@ -0,0 +1,63 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from ...helpers.postgres_helpers import get_postgres_conn +from ...plugins.cliente_contratos import ClienteContratos +from ...plugins.cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Davi", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["empenhos_api"], +) +def api_empenhos_dag() -> None: + """DAG para buscar e armazenar empenhos de uma API no PostgreSQL.""" + + @task + def fetch_empenhos() -> None: + logging.info("[empenhos_ingest_dag.py] Starting fetch_empenhos task") + api = ClienteContratos() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + contratos_ids = db.get_contratos_ids() + + for contrato_id in contratos_ids: + try: + logging.info( + f"[empenhos_ingest_dag.py] Fetching empenhos for contrato ID: " + f"{contrato_id}" + ) + empenhos = api.get_empenhos_by_contrato_id(str(contrato_id)) + + if empenhos: + for empenho in empenhos: + empenho["contrato_id"] = contrato_id + + logging.info( + f"[empenhos_ingest_dag.py] Inserting empenhos for contrato ID: " + f"{contrato_id} into PostgreSQL" + ) + db.insert_data( + empenhos, + "empenhos", + conflict_fields=["id", "contrato_id"], + primary_key=["id", "contrato_id"], + schema="compras_gov", + ) + except Exception as e: + logging.error( + f"[empenhos_ingest_dag.py] Error fetching empenhos for contrato " + f"ID {contrato_id}: {e}" + ) + + fetch_empenhos() + + +dag_instance = api_empenhos_dag() diff --git a/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py new file mode 100644 index 00000000..07abc14b --- /dev/null +++ b/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py @@ -0,0 +1,123 @@ +from typing import Dict, Any, Optional + +from airflow import DAG +from airflow.operators.python import PythonOperator +from airflow.models import Variable +from datetime import datetime, timedelta +import logging +import json +from ...plugins.cliente_email import fetch_and_process_emails +from ...plugins.cliente_postgres import ClientPostgresDB +from ...helpers.postgres_helpers import get_postgres_conn + +# Configurações básicas da DAG +default_args = { + "owner": "Davi", + "depends_on_past": False, + "retries": 1, + "retry_delay": timedelta(minutes=5), +} + +COLUMN_MAPPING = { + 0: "ne_ccor", + 1: "ne_informacao_complementar", + 2: "ne_num_processo", + 3: "ne_ccor_descricao", + 4: "doc_observacao", + 5: "natureza_despesa", + 6: "natureza_despesa_1", + 7: "natureza_despesa_detalhada", + 8: "natureza_despesa_detalhada_1", + 9: "ne_ccor_favorecido", + 10: "ne_ccor_favorecido_1", + 11: "ne_ccor_ano_emissao", + 12: "item_informacao", + 13: "despesas_empenhadas_controle_empenho_saldo_moeda_origem", + 14: "despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem", + 15: "despesas_liquidadas_controle_empenho_saldo_moeda_origem", + 16: "despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem", + 17: "despesas_pagas_controle_empenho_saldo_moeda_origem", + 18: "despesas_pagas_controle_empenho_movim_liquido_moeda_origem", +} + +EMAIL_SUBJECT = "consulta_por_execucao_emp_liq_pago" + + +# Configurações da DAG +with DAG( + dag_id="email_empenhos_tesouro_ingest", + default_args=default_args, + description="Processa anexos dos empenhos vindo do email, formata e insere no db", + schedule_interval="0 13 * * 1-6", + start_date=datetime(2023, 12, 1), + catchup=False, +) as dag: + + def process_email_data(**context: Dict[str, Any]) -> Optional[str]: + creds = json.loads(Variable.get("email_credentials")) + + EMAIL = creds["email"] + PASSWORD = creds["password"] + IMAP_SERVER = creds["imap_server"] + SENDER_EMAIL = creds["sender_email"] + + try: + logging.info("Iniciando o processamento dos emails...") + csv_data = fetch_and_process_emails( + EMAIL, + PASSWORD, + IMAP_SERVER, + SENDER_EMAIL, + EMAIL_SUBJECT, + COLUMN_MAPPING, + ) + if not csv_data: + logging.warning("Nenhum e-mail encontrado com o assunto esperado.") + return None + + logging.info( + "CSV processado com sucesso. Dados encontrados: %s", len(csv_data) + ) + return csv_data + except Exception as e: + logging.error("Erro no processamento dos emails: %s", str(e)) + raise + + def insert_data_to_db(**context: Dict[str, Any]) -> None: + """ + Função para inserir os dados no banco de dados. + Os dados do CSV são recuperados do XCom. + """ + try: + task_instance: Any = context["ti"] + csv_data: Any = task_instance.xcom_pull(task_ids="process_emails") + + if not csv_data: + logging.warning("Nenhum dado para inserir no banco.") + return + + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + db.insert_csv_data(csv_data, "empenhos_tesouro", schema="siafi") + logging.info("Dados inseridos com sucesso no banco de dados.") + except Exception as e: + logging.error("Erro ao inserir dados no banco: %s", str(e)) + raise + + # Tarefa 1: Processar os e-mails e retornar CSV + process_emails_task = PythonOperator( + task_id="process_emails", + python_callable=process_email_data, + provide_context=True, + ) + + # Tarefa 2: Inserir os dados no banco de dados + insert_to_db_task = PythonOperator( + task_id="insert_to_db", + python_callable=insert_data_to_db, + provide_context=True, + ) + + # Fluxo da DAG + process_emails_task >> insert_to_db_task diff --git a/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py new file mode 100644 index 00000000..fcd4c194 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py @@ -0,0 +1,126 @@ +from typing import Optional, Dict, Any + +from airflow import DAG +from airflow.operators.python import PythonOperator +from airflow.models import Variable +from datetime import datetime, timedelta +import logging +import json + +from ...helpers.postgres_helpers import get_postgres_conn +from ...plugins.cliente_email import fetch_and_process_emails +from ...plugins.cliente_postgres import ClientPostgresDB + +# Configurações básicas da DAG +default_args = { + "owner": "Davi", + "depends_on_past": False, + "retries": 1, + "retry_delay": timedelta(minutes=5), +} + +COLUMN_MAPPING = { + 0: "ne_ccor", + 1: "ne_informacao_complementar", + 2: "ne_num_processo", + 3: "ne_ccor_descricao", + 4: "doc_observacao", + 5: "natureza_despesa", + 6: "natureza_despesa_1", + 7: "natureza_despesa_detalhada", + 8: "natureza_despesa_detalhada_1", + 9: "ne_ccor_favorecido", + 10: "ne_ccor_favorecido_1", + 11: "ano_lancamento", + 12: "ne_ccor_mes_emissao", + 13: "ne_ccor_ano_emissao", + 14: "mes_lancamento", + 15: "despesas_empenhadas_controle_empenho_saldo_moeda_origem", + 16: "despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem", + 17: "despesas_liquidadas_controle_empenho_saldo_moeda_origem", + 18: "despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem", + 19: "despesas_pagas_controle_empenho_saldo_moeda_origem", + 20: "despesas_pagas_controle_empenho_movim_liquido_moeda_origem", +} + +EMAIL_SUBJECT = "consulta_por_execucao_emp_liq_pago_mensal" + + +# Configurações da DAG +with DAG( + dag_id="email_estagios_tesouro_ingest", + default_args=default_args, + description="Processa anexos dos estagios vindo do email, formata e insere no db", + schedule_interval="0 13 * * 1-6", + start_date=datetime(2023, 12, 1), + catchup=False, +) as dag: + + def process_email_data(**context: Dict[str, Any]) -> Optional[str]: + creds = json.loads(Variable.get("email_credentials")) + + EMAIL = creds["email"] + PASSWORD = creds["password"] + IMAP_SERVER = creds["imap_server"] + SENDER_EMAIL = creds["sender_email"] + + try: + logging.info("Iniciando o processamento dos emails...") + csv_data = fetch_and_process_emails( + EMAIL, + PASSWORD, + IMAP_SERVER, + SENDER_EMAIL, + EMAIL_SUBJECT, + COLUMN_MAPPING, + ) + if not csv_data: + logging.warning("Nenhum e-mail encontrado com o assunto esperado.") + return None + + logging.info( + "CSV processado com sucesso. Dados encontrados: %s", len(csv_data) + ) + return csv_data + except Exception as e: + logging.error("Erro no processamento dos emails: %s", str(e)) + raise + + def insert_data_to_db(**context: Dict[str, Any]) -> None: + """ + Função para inserir os dados no banco de dados. + Os dados do CSV são recuperados do XCom. + """ + try: + task_instance: Any = context["ti"] + csv_data: Any = task_instance.xcom_pull(task_ids="process_emails") + + if not csv_data: + logging.warning("Nenhum dado para inserir no banco.") + return + + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + db.insert_csv_data(csv_data, "estagios_tesouro", schema="siafi") + logging.info("Dados inseridos com sucesso no banco de dados.") + except Exception as e: + logging.error("Erro ao inserir dados no banco: %s", str(e)) + raise + + # Tarefa 1: Processar os e-mails e retornar CSV + process_emails_task = PythonOperator( + task_id="process_emails", + python_callable=process_email_data, + provide_context=True, + ) + + # Tarefa 2: Inserir os dados no banco de dados + insert_to_db_task = PythonOperator( + task_id="insert_to_db", + python_callable=insert_data_to_db, + provide_context=True, + ) + + # Fluxo da DAG + process_emails_task >> insert_to_db_task diff --git a/airflow_lappis/dags/data_ingest/faturas_ingest_dag.py b/airflow_lappis/dags/data_ingest/faturas_ingest_dag.py new file mode 100644 index 00000000..eba737f8 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/faturas_ingest_dag.py @@ -0,0 +1,58 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from ...plugins.cliente_contratos import ClienteContratos +from ...plugins.cliente_postgres import ClientPostgresDB +from ...helpers.postgres_helpers import get_postgres_conn + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["faturas_api"], +) +def api_faturas_dag() -> None: + """DAG para buscar e armazenar faturas de uma API no PostgreSQL.""" + + @task + def fetch_faturas() -> None: + logging.info("[faturas_ingest_dag.py] Starting fetch_faturas task") + api = ClienteContratos() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + contratos_ids = db.get_contratos_ids() + + for contrato_id in contratos_ids: + try: + logging.info( + f"[faturas_ingest_dag.py] Fetching faturas for contrato ID: " + f"{contrato_id}" + ) + faturas = api.get_faturas_by_contrato_id(str(contrato_id)) + + logging.info( + f"[faturas_ingest_dag.py] Inserting faturas for contrato ID: " + f"{contrato_id} into PostgreSQL" + ) + db.insert_data( + faturas, + "faturas", + conflict_fields=["id"], + primary_key=["id"], + schema="compras_gov", + ) + except Exception as e: + logging.error( + f"[faturas_ingest_dag.py] Error fetching faturas for contrato ID " + f"{contrato_id}: {e}" + ) + + fetch_faturas() + + +dag_instance = api_faturas_dag() diff --git a/airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py b/airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py new file mode 100644 index 00000000..e340a3b8 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py @@ -0,0 +1,92 @@ +import logging +from airflow.decorators import dag, task +from airflow.providers.postgres.hooks.postgres import PostgresHook +from datetime import datetime, timedelta +from ...plugins.cliente_estrutura import ClienteEstrutura +from ...plugins.cliente_postgres import ClientPostgresDB + + +def get_postgres_conn() -> str: + hook = PostgresHook(postgres_conn_id="postgres_default") + conn = hook.connection + port = conn.port + schema = conn.schema + logging.info( + f"[unidade_organizacional_ingest_dag.py] Obtained PostgreSQL connection: " + f"dbname={schema}, user={conn.login}, host={conn.host}, port={port}" + ) + return ( + f"dbname={schema} user={conn.login} password={conn.password} " + f"host={conn.host} port={port}" + ) + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["estrutura_organizacional"], +) +def api_unidade_organizacional_dag() -> None: + """DAG para buscar e armazenar dados da Estrutura Organizacional + de uma API no PostgreSQL.""" + + @task + def fetch_estrutura_organizacional_resumida() -> None: + logging.info( + "[unidade_organizacional_ingest_dag.py] " + "Starting fetch_estrutura_organizacional_resumida task" + ) + api = ClienteEstrutura() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + codigo_poder = "1" + codigo_esfera = "1" + codigo_unidade = "7" + + try: + logging.info( + "[unidade_organizacional_ingest_dag.py] " + "Fetching estrutura organizacional resumida for " + f"codigoUnidade: {codigo_unidade}" + ) + estrutura_resumida = api.get_estrutura_organizacional_resumida( + codigo_poder=codigo_poder, + codigo_esfera=codigo_esfera, + codigo_unidade=codigo_unidade, + ) + if estrutura_resumida: + logging.info( + "[unidade_organizacional_ingest_dag.py] " + "Inserting estrutura organizacional resumida for " + f"codigoUnidade: {codigo_unidade} into PostgreSQL" + ) + db.insert_data( + estrutura_resumida, + "unidade_organizacional", + conflict_fields=["codigoUnidade"], + primary_key=["codigoUnidade"], + schema="siorg", + ) + else: + logging.warning( + "[unidade_organizacional_ingest_dag.py] " + "No estrutura organizacional resumida found for " + f"codigoUnidade: {codigo_unidade}" + ) + except Exception as e: + logging.error( + "[unidade_organizacional_ingest_dag.py] " + "Error fetching estrutura organizacional resumida for " + f"codigoUnidade {codigo_unidade}: {e}" + ) + + fetch_estrutura_organizacional_resumida() + + +dag_instance = api_unidade_organizacional_dag() diff --git a/airflow/dags/dbt/.user.yml b/airflow_lappis/dags/dbt/.user.yml similarity index 100% rename from airflow/dags/dbt/.user.yml rename to airflow_lappis/dags/dbt/.user.yml diff --git a/airflow/dags/dbt/ipea/.user.yml b/airflow_lappis/dags/dbt/ipea/.user.yml similarity index 100% rename from airflow/dags/dbt/ipea/.user.yml rename to airflow_lappis/dags/dbt/ipea/.user.yml diff --git a/airflow/dags/dbt/ipea/dbt_project.yml b/airflow_lappis/dags/dbt/ipea/dbt_project.yml similarity index 100% rename from airflow/dags/dbt/ipea/dbt_project.yml rename to airflow_lappis/dags/dbt/ipea/dbt_project.yml diff --git a/airflow/dags/dbt/ipea/macros/get_custom_schema.sql b/airflow_lappis/dags/dbt/ipea/macros/get_custom_schema.sql similarity index 100% rename from airflow/dags/dbt/ipea/macros/get_custom_schema.sql rename to airflow_lappis/dags/dbt/ipea/macros/get_custom_schema.sql diff --git a/airflow/dags/dbt/ipea/models/contratos/bronze/contratos.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/contratos.sql similarity index 100% rename from airflow/dags/dbt/ipea/models/contratos/bronze/contratos.sql rename to airflow_lappis/dags/dbt/ipea/models/contratos/bronze/contratos.sql diff --git a/airflow/dags/dbt/ipea/models/contratos/bronze/empenhos.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos.sql similarity index 100% rename from airflow/dags/dbt/ipea/models/contratos/bronze/empenhos.sql rename to airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos.sql diff --git a/airflow/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql similarity index 100% rename from airflow/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql rename to airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql diff --git a/airflow/dags/dbt/ipea/models/contratos/bronze/estagios.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/estagios.sql similarity index 100% rename from airflow/dags/dbt/ipea/models/contratos/bronze/estagios.sql rename to airflow_lappis/dags/dbt/ipea/models/contratos/bronze/estagios.sql diff --git a/airflow/dags/dbt/ipea/models/contratos/bronze/faturas.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/faturas.sql similarity index 100% rename from airflow/dags/dbt/ipea/models/contratos/bronze/faturas.sql rename to airflow_lappis/dags/dbt/ipea/models/contratos/bronze/faturas.sql diff --git a/airflow/dags/dbt/ipea/models/contratos/gold/contratos_resumo.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/gold/contratos_resumo.sql similarity index 100% rename from airflow/dags/dbt/ipea/models/contratos/gold/contratos_resumo.sql rename to airflow_lappis/dags/dbt/ipea/models/contratos/gold/contratos_resumo.sql diff --git a/airflow/dags/dbt/ipea/models/contratos/silver/contratos_empenhos.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/silver/contratos_empenhos.sql similarity index 100% rename from airflow/dags/dbt/ipea/models/contratos/silver/contratos_empenhos.sql rename to airflow_lappis/dags/dbt/ipea/models/contratos/silver/contratos_empenhos.sql diff --git a/airflow/dags/dbt/ipea/profiles.yml b/airflow_lappis/dags/dbt/ipea/profiles.yml similarity index 100% rename from airflow/dags/dbt/ipea/profiles.yml rename to airflow_lappis/dags/dbt/ipea/profiles.yml diff --git a/airflow_lappis/helpers/__init__.py b/airflow_lappis/helpers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/airflow_lappis/helpers/postgres_helpers.py b/airflow_lappis/helpers/postgres_helpers.py new file mode 100644 index 00000000..01b9dfe1 --- /dev/null +++ b/airflow_lappis/helpers/postgres_helpers.py @@ -0,0 +1,17 @@ +import logging +from airflow.providers.postgres.hooks.postgres import PostgresHook + + +def get_postgres_conn() -> str: + hook = PostgresHook(postgres_conn_id="postgres_default") + conn = hook.connection + port = conn.port + schema = conn.schema + logging.info( + f"[empenhos_tesouro_ingest_dag.py] Obtained PostgreSQL connection: " + f"dbname={schema}, user={conn.login}, host={conn.host}, port={port}" + ) + return ( + f"dbname={schema} user={conn.login} password={conn.password} " + f"host={conn.host} port={port}" + ) diff --git a/airflow_lappis/plugins/__init__.py b/airflow_lappis/plugins/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/airflow_lappis/plugins/cliente_base.py b/airflow_lappis/plugins/cliente_base.py new file mode 100644 index 00000000..3f8e6269 --- /dev/null +++ b/airflow_lappis/plugins/cliente_base.py @@ -0,0 +1,66 @@ +import logging +import time +import httpx +from typing import Any, Optional, Tuple +from http import HTTPStatus + + +class ClienteBase: + DEFAULT_TIMEOUT = 10 + DEFAULT_MAX_RETRIES = 3 + DEFAULT_SLEEP_SECONDS = 2 + + def __init__(self, base_url: str, headers: Optional[dict] = None) -> None: + self.base_url = base_url + self.client = httpx.Client(base_url=base_url, headers=headers) + logging.info( + f"[cliente_base.py] Initialized ClienteBase with base_url: {base_url}" + ) + + def request( + self, method: str, path: str, **kwargs: Any + ) -> Tuple[HTTPStatus, Optional[dict | list]]: + """ + Faz uma requisição HTTP em até DEFAULT_MAX_RETRIES+1 tentativas. + + Args: + method (str): HTTP Method. + path (str): URL path. + + Returns: + Tuple[HTTPStatus, dict]: status e resposta da requisição HTTP. + """ + kwargs["timeout"] = kwargs.get("timeout", self.DEFAULT_TIMEOUT) + response = None + + for attempt in range(self.DEFAULT_MAX_RETRIES): + try: + logging.info( + f"[cliente_base.py] Attempt {attempt + 1} for {method} " + f"{self.base_url}{path} with kwargs: {kwargs}" + ) + response = self.client.request(method, path, **kwargs) + response.raise_for_status() + logging.info( + f"[cliente_base.py] Request successful with status " + f"{response.status_code}" + ) + return HTTPStatus(response.status_code), response.json() + except httpx.HTTPError as e: + status = response.status_code if response else "Unknown" + logging.warning( + f"[cliente_base.py] API failed with status {status} on " + f"attempt {attempt + 1}. Error: {str(e)}" + ) + if attempt < self.DEFAULT_MAX_RETRIES: + time.sleep(attempt**2 * self.DEFAULT_SLEEP_SECONDS) + else: + logging.error( + f"[cliente_base.py] API failed after " + f"{self.DEFAULT_MAX_RETRIES} attempts. Error: {str(e)}" + ) + raise Exception( + "API failed after the maximum number of attempts!" + ) from e + + return HTTPStatus.INTERNAL_SERVER_ERROR, None diff --git a/airflow_lappis/plugins/cliente_contratos.py b/airflow_lappis/plugins/cliente_contratos.py new file mode 100644 index 00000000..4a9aa477 --- /dev/null +++ b/airflow_lappis/plugins/cliente_contratos.py @@ -0,0 +1,133 @@ +import http +import logging +from .cliente_base import ClienteBase + + +class ClienteContratos(ClienteBase): + BASE_URL = "https://contratos.comprasnet.gov.br/api" + BASE_HEADER = {"accept": "application/json"} + + def __init__(self) -> None: + super().__init__(base_url=ClienteContratos.BASE_URL) + logging.info( + "[cliente_contratos.py] Initialized ClienteContratos with base_url: " + f"{ClienteContratos.BASE_URL}" + ) + + def get_contratos_by_ug(self, ug_code: str) -> list | None: + """ + Obter todos os contratos ativos de uma UG específica. + + Args: + ug_code (str): UG code + + Returns: + list: lista de contratos por ug + """ + endpoint = f"/contrato/ug/{ug_code}" + logging.info(f"[cliente_contratos.py] Fetching contratos for UG code: {ug_code}") + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER + ) + if status == http.HTTPStatus.OK and isinstance(data, list): + logging.info( + "[cliente_contratos.py] Successfully fetched contratos for UG code: " + f"{ug_code}" + ) + return data + else: + logging.warning( + "[cliente_contratos.py] Failed to fetch contratos for UG code: " + f"{ug_code} with status: {status}" + ) + return None + + def get_faturas_by_contrato_id(self, contrato_id: str) -> list | None: + """ + Obter todas as faturas de um contrato específico. + + Args: + contrato_id (str): id do contrato + + Returns: + list: as faturas de um contrato específico. + """ + endpoint = f"/contrato/{contrato_id}/faturas" + logging.info( + f"[cliente_contratos.py] Fetching faturas for contrato ID: {contrato_id}" + ) + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER + ) + if status == http.HTTPStatus.OK and isinstance(data, list): + logging.info( + "[cliente_contratos.py] Successfully fetched faturas for contrato ID: " + f"{contrato_id}" + ) + return data + else: + logging.warning( + "[cliente_contratos.py] Failed to fetch faturas for contrato ID: " + f"{contrato_id} with status: {status}" + ) + return None + + def get_empenhos_by_contrato_id(self, contrato_id: str) -> list | None: + """ + Obter todos os empenhos de um contrato específico. + + Args: + contrato_id (str): id do contrato + + Returns: + list: os empenhos de um contrato específico. + """ + endpoint = f"/contrato/{contrato_id}/empenhos" + logging.info( + f"[cliente_contratos.py] Fetching empenhos for contrato ID: {contrato_id}" + ) + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER + ) + if status == http.HTTPStatus.OK and isinstance(data, list): + logging.info( + "[cliente_contratos.py] Successfully fetched empenhos for contrato ID: " + f"{contrato_id}" + ) + return data + else: + logging.warning( + "[cliente_contratos.py] Failed to fetch empenhos for contrato ID: " + f"{contrato_id} with status: {status}" + ) + return None + + def get_cronograma_by_contrato_id(self, contrato_id: str) -> list | None: + """ + Obter todos os cronogramas de um contrato específico. + + Args: + contrato_id (str): id do contrato + + Returns: + list: cronogramas de um contrato específico. + """ + endpoint = f"/contrato/{contrato_id}/cronograma" + logging.info( + f"[cliente_contratos.py] Fetching cronograma for contrato ID: {contrato_id}" + ) + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER + ) + if status == http.HTTPStatus.OK and isinstance(data, list): + logging.info( + "[cliente_contratos.py] Successfully fetched cronograma for contrato ID: " + f"{contrato_id}" + ) + return data + else: + logging.warning( + "[cliente_contratos.py] Failed to fetch cronograma for contrato ID: " + f"{contrato_id} with status: {status}" + ) + return None diff --git a/airflow_lappis/plugins/cliente_email.py b/airflow_lappis/plugins/cliente_email.py new file mode 100644 index 00000000..0f079c12 --- /dev/null +++ b/airflow_lappis/plugins/cliente_email.py @@ -0,0 +1,124 @@ +import logging +import io +import zipfile +from typing import Optional +from typing_extensions import Buffer + +import pandas as pd +from imap_tools import MailBox, AND, MailMessage +import chardet +import pytz +from datetime import datetime + +# Configuração do log +logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" +) + + +def format_csv(csv_data: str, column_mapping: dict) -> pd.DataFrame: + """Formata um arquivo CSV conforme mapeamento de colunas.""" + try: + logging.info("Formatando CSV na memória...") + df = pd.read_csv(io.StringIO(csv_data), skiprows=5, header=None) + df.columns = pd.Index( + [column_mapping.get(i, f"col_{i}") for i in range(len(df.columns))] + ) + logging.info("CSV formatado com sucesso.") + return df + except Exception as e: + logging.error("Erro ao formatar CSV.") + raise ValueError(f"Erro ao formatar CSV: {e}") + + +def extract_csv_from_zip( + zip_payload: Buffer, column_mapping: dict +) -> Optional[pd.DataFrame]: + """Extrai e formata arquivos CSV de um arquivo ZIP.""" + try: + logging.info("Abrindo arquivo ZIP...") + with zipfile.ZipFile(io.BytesIO(zip_payload)) as zip_file: + for file_name in zip_file.namelist(): + if file_name.endswith(".csv"): + logging.info(f"Processando arquivo CSV: {file_name}") + with zip_file.open(file_name) as csv_file: + raw_data = csv_file.read() + encoding = chardet.detect(raw_data)["encoding"] + logging.info(f"Codificação detectada: {encoding}") + decoded_data = raw_data.decode(encoding) + return format_csv(decoded_data, column_mapping) + logging.warning("Nenhum arquivo CSV encontrado no ZIP.") + return None + except Exception as e: + logging.error("Erro ao processar arquivo ZIP.") + raise ValueError(f"Erro ao extrair CSV do ZIP: {e}") + + +def fetch_emails( + imap_server: str, email: str, password: str, sender_email: str, subject: str +) -> Optional[MailMessage]: + """Busca e-mails no servidor IMAP conforme critérios especificados.""" + local_tz = pytz.timezone("America/Sao_Paulo") + today = datetime.now(local_tz).date() + logging.info( + f"Conectando ao servidor IMAP para buscar e-mails de {sender_email} em {today}." + ) + + try: + with MailBox(imap_server).login(email, password) as mailbox: + emails: list[MailMessage] = list( + mailbox.fetch(AND(date=today, from_=sender_email)) + ) + for email_item in emails: + logging.info(f"E-mail encontrado: Assunto - '{email_item.subject}'") + if email_item.subject == subject: + return email_item + logging.warning("Nenhum e-mail com o assunto esperado encontrado.") + except Exception as e: + logging.error("Erro ao buscar e-mails.") + raise ConnectionError(f"Erro ao buscar e-mails: {e}") + return None + + +def process_email_attachments( + email_item: MailMessage, column_mapping: dict +) -> Optional[pd.DataFrame]: + """Processa os anexos ZIP de um e-mail e retorna o CSV formatado.""" + if not email_item: + logging.warning("Nenhum e-mail válido fornecido para processamento.") + return None + + for attachment in email_item.attachments: + if attachment.filename.endswith(".zip"): + logging.info(f"Anexo ZIP encontrado: {attachment.filename}. Processando...") + return extract_csv_from_zip(attachment.payload, column_mapping) + else: + logging.info(f"Anexo ignorado: {attachment.filename} (não é ZIP).") + + logging.warning("Nenhum anexo ZIP encontrado no e-mail.") + return None + + +def fetch_and_process_emails( + email: str, + password: str, + imap_server: str, + sender_email: str, + subject: str, + column_mapping: dict, +) -> Optional[str]: + """Orquestra a busca de e-mails, + processamento de anexos e retorno do CSV formatado.""" + try: + email_item = fetch_emails(imap_server, email, password, sender_email, subject) + csv_data = process_email_attachments(email_item, column_mapping) + + if csv_data is not None: + logging.info("E-mail processado com sucesso. Retornando CSV formatado.") + return csv_data.to_csv(index=False) + else: + logging.info("Nenhum CSV processado.") + return None + except Exception as e: + logging.error("Erro durante o processamento de e-mails.") + raise RuntimeError(f"Erro ao processar e-mails: {e}") diff --git a/airflow/plugins/cliente_estrutura.py b/airflow_lappis/plugins/cliente_estrutura.py similarity index 97% rename from airflow/plugins/cliente_estrutura.py rename to airflow_lappis/plugins/cliente_estrutura.py index 7522b657..f0f19b67 100644 --- a/airflow/plugins/cliente_estrutura.py +++ b/airflow_lappis/plugins/cliente_estrutura.py @@ -1,7 +1,7 @@ import http from typing import Optional -from cliente_base import ClienteBase +from .cliente_base import ClienteBase class ClienteEstrutura(ClienteBase): diff --git a/airflow_lappis/plugins/cliente_postgres.py b/airflow_lappis/plugins/cliente_postgres.py new file mode 100644 index 00000000..41855c03 --- /dev/null +++ b/airflow_lappis/plugins/cliente_postgres.py @@ -0,0 +1,228 @@ +import logging +from typing import Any, Dict, List, Optional, Tuple +import psycopg2 +from pandas import json_normalize +import pandas as pd +import io + + +class ClientPostgresDB: + """Client for interacting with PostgreSQL database.""" + + SEPARATOR = "__" + TYPE_MAP = {int: "BIGINT", float: "NUMERIC", bool: "BOOLEAN"} + + @staticmethod + def _get_column_type(value: Any) -> str: + """ + Determine PostgreSQL column type from Python value. + + Args: + value: Python value to analyze + + Returns: + PostgreSQL column type as string + """ + return ClientPostgresDB.TYPE_MAP.get(type(value), "TEXT") + + def _flatten_data(self, data: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + """ + Flatten nested JSON data. + + Args: + data (List[Dict[str, Any]]): List of nested dictionaries. + + Returns: + List[Dict[str, Any]]: List of flat dictionaries. + """ + return list( + map( + lambda d: { + str(k): v if type(v) is not list else str(v) for k, v in d.items() + }, + json_normalize(data, sep=ClientPostgresDB.SEPARATOR).to_dict( + orient="records" + ), + ) + ) + + def __init__(self, conn_str: str) -> None: + self.conn_str = conn_str + logging.info( + f"[cliente_postgres.py] Initialized ClientPostgresDB with conn_str: " + f"{conn_str}" + ) + + def create_table_if_not_exists( + self, + sample_data: Dict[str, Any], + table_name: str, + primary_key: Optional[List[str]] = None, + schema: str = "raw", + ) -> None: + """Create table dynamically based on data structure. + + Args: + sample_data: Sample data to determine schema + table_name: Name of table to create + primary_key: List of primary key column names + schema: Database schema name + """ + with psycopg2.connect(self.conn_str) as conn: + with conn.cursor() as cursor: + cursor.execute(f"CREATE SCHEMA IF NOT EXISTS {schema};") + logging.info(f"[cliente_postgres.py] Schema {schema} ensured to exist") + + flattened_sample = self._flatten_data([sample_data])[0] + column_definitions: List[str] = [] + + for column in flattened_sample.keys(): + column_definitions.append(f"{column} TEXT") + + if primary_key: + pk_str = ", ".join(primary_key) + column_definitions.append(f"PRIMARY KEY ({pk_str})") + + create_table_query = ( + f"CREATE TABLE IF NOT EXISTS {schema}.{table_name} (" + f"{', '.join(column_definitions)});" + ) + + try: + cursor.execute(create_table_query) + logging.info( + f"[cliente_postgres.py] Table {schema}.{table_name} created " + f"or already exists" + ) + except psycopg2.Error as err: + logging.error( + f"[cliente_postgres.py] Failed to create table {schema}." + f"{table_name}. Error: {str(err)}" + ) + raise RuntimeError( + f"Failed to create table {schema}.{table_name}" + ) from err + + def insert_data( + self, + data: List[Dict[str, Any]], + table_name: str, + conflict_fields: Optional[List[str]] = None, + primary_key: Optional[List[str]] = None, + schema: str = "raw", + ) -> None: + """Insert data into database table. + + Args: + data: List of dictionaries to insert + table_name: Target table name + conflict_fields: List of column names for conflict resolution + primary_key: List of primary key column names + schema: Database schema name + """ + if not data: + logging.warning( + f"[cliente_postgres.py] No data to insert into {schema}.{table_name}" + ) + return + + self.create_table_if_not_exists( + data[0], table_name, primary_key=primary_key, schema=schema + ) + + flattened_data = self._flatten_data(data) + columns = list(flattened_data[0].keys()) + values = [tuple(item.values()) for item in flattened_data] + + sql = f"INSERT INTO {schema}.{table_name} ({', '.join(columns)}) VALUES %s" + + if conflict_fields: + conflict_str = ", ".join(conflict_fields) + sql += f" ON CONFLICT ({conflict_str}) DO NOTHING" + + with psycopg2.connect(self.conn_str) as conn: + with conn.cursor() as cursor: + try: + psycopg2.extras.execute_values(cursor, sql, values) + conn.commit() + logging.info( + f"[cliente_postgres.py] Inserted data into {schema}." + f"{table_name}" + ) + except psycopg2.Error as err: + logging.error( + f"[cliente_postgres.py] Failed to insert data into {schema}." + f"{table_name}. Error: {str(err)}" + ) + raise RuntimeError( + f"Failed to insert data into {schema}.{table_name}" + ) from err + + def execute_query(self, query: str) -> List[Tuple[Any, ...]]: + """Execute a query and return the results. + + Args: + query: SQL query to execute + + Returns: + List of tuples containing the query results + """ + logging.info(f"[cliente_postgres.py] Executing query: {query}") + with psycopg2.connect(self.conn_str) as conn: + with conn.cursor() as cursor: + cursor.execute(query) + results = cursor.fetchall() + logging.info( + f"[cliente_postgres.py] Query executed successfully, fetched " + f"{len(results)} rows" + ) + return results + + def get_contratos_ids(self) -> List[int]: + """Extrai todos os IDs de contratos da tabela contratos.""" + query = "SELECT id FROM raw.contratos" + + with psycopg2.connect(self.conn_str) as conn: + with conn.cursor() as cursor: + cursor.execute(query) + contratos_ids = [row[0] for row in cursor.fetchall()] + return contratos_ids + + def drop_table_if_exists(self, table_name: str, schema: str = "raw") -> None: + """Remove a tabela se ela existir.""" + conn = psycopg2.connect(self.conn_str) + cursor = conn.cursor() + drop_table_query = f"DROP TABLE IF EXISTS {schema}.{table_name};" + try: + cursor.execute(drop_table_query) + conn.commit() + print(f"Tabela {schema}.{table_name} removida com sucesso.") + except Exception as e: + print(f"Erro ao remover a tabela {schema}.{table_name}: {e}") + finally: + cursor.close() + conn.close() + + def insert_csv_data( + self, csv_data: str, table_name: str, schema: str = "raw" + ) -> None: + """ + Insere dados de um CSV no banco de dados, garantindo que a tabela seja criada. + Se a tabela existir, ela será removida antes da inserção dos novos dados. + + Args: + csv_data (str): Dados do CSV como string. + table_name (str): Nome da tabela de destino. + schema (str): Nome do schema do banco (padrão: "raw"). + """ + # Converte o CSV para DataFrame + df = pd.read_csv(io.StringIO(csv_data)) + + # Converte o DataFrame para lista de dicionários + data = df.to_dict(orient="records") + + # Remove a tabela existente + self.drop_table_if_exists(table_name, schema) + + # Insere os novos dados + self.insert_data(data, table_name, primary_key=None, schema=schema) diff --git a/airflow/plugins/cliente_siafi.py b/airflow_lappis/plugins/cliente_siafi.py similarity index 98% rename from airflow/plugins/cliente_siafi.py rename to airflow_lappis/plugins/cliente_siafi.py index 108f0449..15129b13 100644 --- a/airflow/plugins/cliente_siafi.py +++ b/airflow_lappis/plugins/cliente_siafi.py @@ -4,7 +4,7 @@ import requests from httpx import HTTPStatusError -from cliente_base import ClienteBase +from .cliente_base import ClienteBase class ClienteSiafi(ClienteBase): diff --git a/airflow/plugins/cliente_siape.py b/airflow_lappis/plugins/cliente_siape.py similarity index 100% rename from airflow/plugins/cliente_siape.py rename to airflow_lappis/plugins/cliente_siape.py diff --git a/jupyter/test_apis.ipynb b/jupyter/test_apis.ipynb index d80097fb..9815e8ba 100644 --- a/jupyter/test_apis.ipynb +++ b/jupyter/test_apis.ipynb @@ -990,8 +990,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-01-26T12:02:14.004473Z", - "start_time": "2025-01-26T12:02:12.856870Z" + "end_time": "2025-01-31T19:54:03.665676Z", + "start_time": "2025-01-31T19:54:01.401840Z" } }, "cell_type": "code", @@ -1017,7 +1017,7 @@ "traceback": [ "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", "\u001B[0;31mHTTPError\u001B[0m Traceback (most recent call last)", - "Cell \u001B[0;32mIn[3], line 11\u001B[0m\n\u001B[1;32m 9\u001B[0m session\u001B[38;5;241m.\u001B[39mauth \u001B[38;5;241m=\u001B[39m HTTPBasicAuth(\u001B[38;5;124m'\u001B[39m\u001B[38;5;124m9b47d48f-26fe-49d8-8c74-0a3d91bb06b0\u001B[39m\u001B[38;5;124m'\u001B[39m, \u001B[38;5;124m'\u001B[39m\u001B[38;5;124m9828538e-7181-4a84-9f14-d0d3a08b8125\u001B[39m\u001B[38;5;124m'\u001B[39m)\n\u001B[1;32m 10\u001B[0m transport \u001B[38;5;241m=\u001B[39m Transport(session\u001B[38;5;241m=\u001B[39msession)\n\u001B[0;32m---> 11\u001B[0m bearer_client \u001B[38;5;241m=\u001B[39m \u001B[43mClient\u001B[49m\u001B[43m(\u001B[49m\u001B[43mBEARER_ENDPOINT\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mtransport\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mtransport\u001B[49m\u001B[43m)\u001B[49m\n", + "Cell \u001B[0;32mIn[1], line 11\u001B[0m\n\u001B[1;32m 9\u001B[0m session\u001B[38;5;241m.\u001B[39mauth \u001B[38;5;241m=\u001B[39m HTTPBasicAuth(\u001B[38;5;124m'\u001B[39m\u001B[38;5;124m9b47d48f-26fe-49d8-8c74-0a3d91bb06b0\u001B[39m\u001B[38;5;124m'\u001B[39m, \u001B[38;5;124m'\u001B[39m\u001B[38;5;124m9828538e-7181-4a84-9f14-d0d3a08b8125\u001B[39m\u001B[38;5;124m'\u001B[39m)\n\u001B[1;32m 10\u001B[0m transport \u001B[38;5;241m=\u001B[39m Transport(session\u001B[38;5;241m=\u001B[39msession)\n\u001B[0;32m---> 11\u001B[0m bearer_client \u001B[38;5;241m=\u001B[39m \u001B[43mClient\u001B[49m\u001B[43m(\u001B[49m\u001B[43mBEARER_ENDPOINT\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mtransport\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mtransport\u001B[49m\u001B[43m)\u001B[49m\n", "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/site-packages/zeep/client.py:76\u001B[0m, in \u001B[0;36mClient.__init__\u001B[0;34m(self, wsdl, wsse, transport, service_name, port_name, plugins, settings)\u001B[0m\n\u001B[1;32m 74\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mwsdl \u001B[38;5;241m=\u001B[39m wsdl\n\u001B[1;32m 75\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[0;32m---> 76\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mwsdl \u001B[38;5;241m=\u001B[39m \u001B[43mDocument\u001B[49m\u001B[43m(\u001B[49m\u001B[43mwsdl\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mtransport\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43msettings\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43msettings\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 77\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mwsse \u001B[38;5;241m=\u001B[39m wsse\n\u001B[1;32m 78\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mplugins \u001B[38;5;241m=\u001B[39m plugins \u001B[38;5;28;01mif\u001B[39;00m plugins \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m \u001B[38;5;28;01melse\u001B[39;00m []\n", "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/site-packages/zeep/wsdl/wsdl.py:86\u001B[0m, in \u001B[0;36mDocument.__init__\u001B[0;34m(self, location, transport, base, settings)\u001B[0m\n\u001B[1;32m 77\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_definitions \u001B[38;5;241m=\u001B[39m (\n\u001B[1;32m 78\u001B[0m {}\n\u001B[1;32m 79\u001B[0m ) \u001B[38;5;66;03m# type: typing.Dict[typing.Tuple[str, str], \"Definition\"]\u001B[39;00m\n\u001B[1;32m 80\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mtypes \u001B[38;5;241m=\u001B[39m Schema(\n\u001B[1;32m 81\u001B[0m node\u001B[38;5;241m=\u001B[39m\u001B[38;5;28;01mNone\u001B[39;00m,\n\u001B[1;32m 82\u001B[0m transport\u001B[38;5;241m=\u001B[39m\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mtransport,\n\u001B[1;32m 83\u001B[0m location\u001B[38;5;241m=\u001B[39m\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mlocation,\n\u001B[1;32m 84\u001B[0m settings\u001B[38;5;241m=\u001B[39m\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39msettings,\n\u001B[1;32m 85\u001B[0m )\n\u001B[0;32m---> 86\u001B[0m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mload\u001B[49m\u001B[43m(\u001B[49m\u001B[43mlocation\u001B[49m\u001B[43m)\u001B[49m\n", "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/site-packages/zeep/wsdl/wsdl.py:89\u001B[0m, in \u001B[0;36mDocument.load\u001B[0;34m(self, location)\u001B[0m\n\u001B[1;32m 88\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;21mload\u001B[39m(\u001B[38;5;28mself\u001B[39m, location):\n\u001B[0;32m---> 89\u001B[0m document \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_get_xml_document\u001B[49m\u001B[43m(\u001B[49m\u001B[43mlocation\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 91\u001B[0m root_definitions \u001B[38;5;241m=\u001B[39m Definition(\u001B[38;5;28mself\u001B[39m, document, \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mlocation)\n\u001B[1;32m 92\u001B[0m root_definitions\u001B[38;5;241m.\u001B[39mresolve_imports()\n", @@ -1030,13 +1030,13 @@ ] } ], - "execution_count": 3 + "execution_count": 1 }, { "metadata": { "ExecuteTime": { - "end_time": "2025-01-27T08:26:47.857897Z", - "start_time": "2025-01-27T08:26:46.737498Z" + "end_time": "2025-01-31T19:54:54.917861Z", + "start_time": "2025-01-31T19:54:53.265544Z" } }, "cell_type": "code", @@ -1069,7 +1069,7 @@ "client = Client(SOAP_ENDPOINT, transport=transport)\n", "\n", "# Make service calls\n", - "result = client.service.method_name()" + "result = client.service.ConsultaDadosAfastamento()" ], "id": "e0e543b67f021fb7", "outputs": [ @@ -1097,13 +1097,13 @@ "\u001B[0;31mJSONDecodeError\u001B[0m: Expecting value: line 1 column 1 (char 0)", "\nDuring handling of the above exception, another exception occurred:\n", "\u001B[0;31mJSONDecodeError\u001B[0m Traceback (most recent call last)", - "Cell \u001B[0;32mIn[60], line 18\u001B[0m\n\u001B[1;32m 12\u001B[0m auth_response \u001B[38;5;241m=\u001B[39m requests\u001B[38;5;241m.\u001B[39mget(\n\u001B[1;32m 13\u001B[0m BEARER_ENDPOINT,\n\u001B[1;32m 14\u001B[0m auth\u001B[38;5;241m=\u001B[39m(\u001B[38;5;124m'\u001B[39m\u001B[38;5;124m9b47d48f-26fe-49d8-8c74-0a3d91bb06b0\u001B[39m\u001B[38;5;124m'\u001B[39m, \u001B[38;5;124m'\u001B[39m\u001B[38;5;124m9828538e-7181-4a84-9f14-d0d3a08b8125\u001B[39m\u001B[38;5;124m'\u001B[39m),\n\u001B[1;32m 15\u001B[0m headers\u001B[38;5;241m=\u001B[39m{\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mContent-Type\u001B[39m\u001B[38;5;124m'\u001B[39m: \u001B[38;5;124m'\u001B[39m\u001B[38;5;124mapplication/x-www-form-urlencoded\u001B[39m\u001B[38;5;124m'\u001B[39m}\n\u001B[1;32m 16\u001B[0m )\n\u001B[1;32m 17\u001B[0m \u001B[38;5;28mprint\u001B[39m(auth_response\u001B[38;5;241m.\u001B[39mtext)\n\u001B[0;32m---> 18\u001B[0m token \u001B[38;5;241m=\u001B[39m \u001B[43mauth_response\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mjson\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m[\u001B[38;5;124m'\u001B[39m\u001B[38;5;124maccess_token\u001B[39m\u001B[38;5;124m'\u001B[39m] \u001B[38;5;66;03m# Adjust based on response format\u001B[39;00m\n\u001B[1;32m 20\u001B[0m \u001B[38;5;66;03m# Setup SOAP client with token\u001B[39;00m\n\u001B[1;32m 21\u001B[0m session \u001B[38;5;241m=\u001B[39m Session()\n", + "Cell \u001B[0;32mIn[3], line 18\u001B[0m\n\u001B[1;32m 12\u001B[0m auth_response \u001B[38;5;241m=\u001B[39m requests\u001B[38;5;241m.\u001B[39mget(\n\u001B[1;32m 13\u001B[0m BEARER_ENDPOINT,\n\u001B[1;32m 14\u001B[0m auth\u001B[38;5;241m=\u001B[39m(\u001B[38;5;124m'\u001B[39m\u001B[38;5;124m9b47d48f-26fe-49d8-8c74-0a3d91bb06b0\u001B[39m\u001B[38;5;124m'\u001B[39m, \u001B[38;5;124m'\u001B[39m\u001B[38;5;124m9828538e-7181-4a84-9f14-d0d3a08b8125\u001B[39m\u001B[38;5;124m'\u001B[39m),\n\u001B[1;32m 15\u001B[0m headers\u001B[38;5;241m=\u001B[39m{\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mContent-Type\u001B[39m\u001B[38;5;124m'\u001B[39m: \u001B[38;5;124m'\u001B[39m\u001B[38;5;124mapplication/x-www-form-urlencoded\u001B[39m\u001B[38;5;124m'\u001B[39m}\n\u001B[1;32m 16\u001B[0m )\n\u001B[1;32m 17\u001B[0m \u001B[38;5;28mprint\u001B[39m(auth_response\u001B[38;5;241m.\u001B[39mtext)\n\u001B[0;32m---> 18\u001B[0m token \u001B[38;5;241m=\u001B[39m \u001B[43mauth_response\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mjson\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m[\u001B[38;5;124m'\u001B[39m\u001B[38;5;124maccess_token\u001B[39m\u001B[38;5;124m'\u001B[39m] \u001B[38;5;66;03m# Adjust based on response format\u001B[39;00m\n\u001B[1;32m 20\u001B[0m \u001B[38;5;66;03m# Setup SOAP client with token\u001B[39;00m\n\u001B[1;32m 21\u001B[0m session \u001B[38;5;241m=\u001B[39m Session()\n", "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/site-packages/requests/models.py:978\u001B[0m, in \u001B[0;36mResponse.json\u001B[0;34m(self, **kwargs)\u001B[0m\n\u001B[1;32m 974\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m complexjson\u001B[38;5;241m.\u001B[39mloads(\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mtext, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs)\n\u001B[1;32m 975\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m JSONDecodeError \u001B[38;5;28;01mas\u001B[39;00m e:\n\u001B[1;32m 976\u001B[0m \u001B[38;5;66;03m# Catch JSON-related errors and raise as requests.JSONDecodeError\u001B[39;00m\n\u001B[1;32m 977\u001B[0m \u001B[38;5;66;03m# This aliases json.JSONDecodeError and simplejson.JSONDecodeError\u001B[39;00m\n\u001B[0;32m--> 978\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m RequestsJSONDecodeError(e\u001B[38;5;241m.\u001B[39mmsg, e\u001B[38;5;241m.\u001B[39mdoc, e\u001B[38;5;241m.\u001B[39mpos)\n", "\u001B[0;31mJSONDecodeError\u001B[0m: Expecting value: line 1 column 1 (char 0)" ] } ], - "execution_count": 60 + "execution_count": 3 }, { "metadata": { diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 1215375e..00000000 --- a/mypy.ini +++ /dev/null @@ -1,2 +0,0 @@ -[mypy] -ignore_missing_imports = True \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 6d82bb46..d95e2c36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,12 +9,14 @@ readme = "README.md" [tool.poetry.dependencies] python = "~3.11" apache-airflow = "2.8.1" +apache-airflow-providers-postgres = "5.14.0" dbt-core = "*" dbt-postgres = "*" pandas = "*" requests = "*" sqlalchemy = "*" zeep = "*" +imap-tools = "*" [tool.poetry.group.dev.dependencies] black = "*" diff --git a/requirements.txt b/requirements.txt index 5f433440..cfe3a74c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,16 +10,17 @@ apache-airflow-providers-common-sql==1.20.0 ; python_version >= "3.11" and pytho apache-airflow-providers-ftp==3.11.1 ; python_version >= "3.11" and python_version < "3.12" apache-airflow-providers-http==4.13.3 ; python_version >= "3.11" and python_version < "3.12" apache-airflow-providers-imap==3.7.0 ; python_version >= "3.11" and python_version < "3.12" +apache-airflow-providers-postgres==5.14.0 ; python_version >= "3.11" and python_version < "3.12" apache-airflow-providers-sqlite==3.9.1 ; python_version >= "3.11" and python_version < "3.12" apache-airflow==2.8.1 ; python_version >= "3.11" and python_version < "3.12" apispec[yaml]==6.8.1 ; python_version >= "3.11" and python_version < "3.12" argcomplete==3.5.3 ; python_version >= "3.11" and python_version < "3.12" asgiref==3.8.1 ; python_version >= "3.11" and python_version < "3.12" attrs==25.1.0 ; python_version >= "3.11" and python_version < "3.12" -babel==2.16.0 ; python_version >= "3.11" and python_version < "3.12" +babel==2.17.0 ; python_version >= "3.11" and python_version < "3.12" blinker==1.9.0 ; python_version >= "3.11" and python_version < "3.12" cachelib==0.9.0 ; python_version >= "3.11" and python_version < "3.12" -certifi==2024.12.14 ; python_version >= "3.11" and python_version < "3.12" +certifi==2025.1.31 ; python_version >= "3.11" and python_version < "3.12" cffi==1.17.1 ; python_version >= "3.11" and python_version < "3.12" and platform_python_implementation != "PyPy" charset-normalizer==3.4.1 ; python_version >= "3.11" and python_version < "3.12" click==8.1.8 ; python_version >= "3.11" and python_version < "3.12" @@ -34,12 +35,12 @@ cryptography==44.0.0 ; python_version >= "3.11" and python_version < "3.12" daff==1.3.46 ; python_version >= "3.11" and python_version < "3.12" dbt-adapters==1.13.2 ; python_version >= "3.11" and python_version < "3.12" dbt-common==1.14.0 ; python_version >= "3.11" and python_version < "3.12" -dbt-core==1.9.1 ; python_version >= "3.11" and python_version < "3.12" +dbt-core==1.9.2 ; python_version >= "3.11" and python_version < "3.12" dbt-extractor==0.5.1 ; python_version >= "3.11" and python_version < "3.12" dbt-postgres==1.9.0 ; python_version >= "3.11" and python_version < "3.12" dbt-semantic-interfaces==0.7.4 ; python_version >= "3.11" and python_version < "3.12" deepdiff==7.0.1 ; python_version >= "3.11" and python_version < "3.12" -deprecated==1.2.17 ; python_version >= "3.11" and python_version < "3.12" +deprecated==1.2.18 ; python_version >= "3.11" and python_version < "3.12" dill==0.3.9 ; python_version >= "3.11" and python_version < "3.12" dnspython==2.7.0 ; python_version >= "3.11" and python_version < "3.12" email-validator==1.3.1 ; python_version >= "3.11" and python_version < "3.12" @@ -54,7 +55,7 @@ flask-sqlalchemy==2.5.1 ; python_version >= "3.11" and python_version < "3.12" flask-wtf==1.2.2 ; python_version >= "3.11" and python_version < "3.12" flask==2.2.5 ; python_version >= "3.11" and python_version < "3.12" frozenlist==1.5.0 ; python_version >= "3.11" and python_version < "3.12" -fsspec==2024.12.0 ; python_version >= "3.11" and python_version < "3.12" +fsspec==2025.2.0 ; python_version >= "3.11" and python_version < "3.12" google-re2==1.1.20240702 ; python_version >= "3.11" and python_version < "3.12" googleapis-common-protos==1.66.0 ; python_version >= "3.11" and python_version < "3.12" greenlet==3.1.1 ; python_version >= "3.11" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32") and python_version < "3.12" @@ -64,6 +65,7 @@ h11==0.14.0 ; python_version >= "3.11" and python_version < "3.12" httpcore==1.0.7 ; python_version >= "3.11" and python_version < "3.12" httpx==0.28.1 ; python_version >= "3.11" and python_version < "3.12" idna==3.10 ; python_version >= "3.11" and python_version < "3.12" +imap-tools==1.9.1 ; python_version >= "3.11" and python_version < "3.12" importlib-metadata==6.11.0 ; python_version >= "3.11" and python_version < "3.12" inflection==0.5.1 ; python_version >= "3.11" and python_version < "3.12" isodate==0.6.1 ; python_version >= "3.11" and python_version < "3.12" @@ -77,13 +79,13 @@ limits==4.0.1 ; python_version >= "3.11" and python_version < "3.12" linkify-it-py==2.0.3 ; python_version >= "3.11" and python_version < "3.12" lockfile==0.12.2 ; python_version >= "3.11" and python_version < "3.12" lxml==5.3.0 ; python_version >= "3.11" and python_version < "3.12" -mako==1.3.8 ; python_version >= "3.11" and python_version < "3.12" +mako==1.3.9 ; python_version >= "3.11" and python_version < "3.12" markdown-it-py==3.0.0 ; python_version >= "3.11" and python_version < "3.12" markdown==3.7 ; python_version >= "3.11" and python_version < "3.12" markupsafe==3.0.2 ; python_version >= "3.11" and python_version < "3.12" marshmallow-oneofschema==3.1.1 ; python_version >= "3.11" and python_version < "3.12" marshmallow-sqlalchemy==0.26.1 ; python_version >= "3.11" and python_version < "3.12" -marshmallow==3.26.0 ; python_version >= "3.11" and python_version < "3.12" +marshmallow==3.26.1 ; python_version >= "3.11" and python_version < "3.12" mashumaro[msgpack]==3.14 ; python_version >= "3.11" and python_version < "3.12" mdit-py-plugins==0.4.2 ; python_version >= "3.11" and python_version < "3.12" mdurl==0.1.2 ; python_version >= "3.11" and python_version < "3.12" @@ -93,14 +95,14 @@ msgspec==0.19.0 ; python_version >= "3.11" and python_version < "3.12" multidict==6.1.0 ; python_version >= "3.11" and python_version < "3.12" networkx==3.4.2 ; python_version >= "3.11" and python_version < "3.12" numpy==2.2.2 ; python_version == "3.11" -opentelemetry-api==1.29.0 ; python_version >= "3.11" and python_version < "3.12" -opentelemetry-exporter-otlp-proto-common==1.29.0 ; python_version >= "3.11" and python_version < "3.12" -opentelemetry-exporter-otlp-proto-grpc==1.29.0 ; python_version >= "3.11" and python_version < "3.12" -opentelemetry-exporter-otlp-proto-http==1.29.0 ; python_version >= "3.11" and python_version < "3.12" -opentelemetry-exporter-otlp==1.29.0 ; python_version >= "3.11" and python_version < "3.12" -opentelemetry-proto==1.29.0 ; python_version >= "3.11" and python_version < "3.12" -opentelemetry-sdk==1.29.0 ; python_version >= "3.11" and python_version < "3.12" -opentelemetry-semantic-conventions==0.50b0 ; python_version >= "3.11" and python_version < "3.12" +opentelemetry-api==1.30.0 ; python_version >= "3.11" and python_version < "3.12" +opentelemetry-exporter-otlp-proto-common==1.30.0 ; python_version >= "3.11" and python_version < "3.12" +opentelemetry-exporter-otlp-proto-grpc==1.30.0 ; python_version >= "3.11" and python_version < "3.12" +opentelemetry-exporter-otlp-proto-http==1.30.0 ; python_version >= "3.11" and python_version < "3.12" +opentelemetry-exporter-otlp==1.30.0 ; python_version >= "3.11" and python_version < "3.12" +opentelemetry-proto==1.30.0 ; python_version >= "3.11" and python_version < "3.12" +opentelemetry-sdk==1.30.0 ; python_version >= "3.11" and python_version < "3.12" +opentelemetry-semantic-conventions==0.51b0 ; python_version >= "3.11" and python_version < "3.12" ordered-set==4.1.0 ; python_version >= "3.11" and python_version < "3.12" packaging==24.2 ; python_version >= "3.11" and python_version < "3.12" pandas==2.2.3 ; python_version >= "3.11" and python_version < "3.12" @@ -124,7 +126,7 @@ python-dateutil==2.9.0.post0 ; python_version >= "3.11" and python_version < "3. python-nvd3==0.16.0 ; python_version >= "3.11" and python_version < "3.12" python-slugify==8.0.4 ; python_version >= "3.11" and python_version < "3.12" pytimeparse==1.1.8 ; python_version >= "3.11" and python_version < "3.12" -pytz==2024.2 ; python_version >= "3.11" and python_version < "3.12" +pytz==2025.1 ; python_version >= "3.11" and python_version < "3.12" pyyaml==6.0.2 ; python_version >= "3.11" and python_version < "3.12" referencing==0.36.2 ; python_version >= "3.11" and python_version < "3.12" requests-file==2.1.0 ; python_version >= "3.11" and python_version < "3.12" From 9edbf4171579faad6be2d5dc4c0c2b29b46eed8f Mon Sep 17 00:00:00 2001 From: MatDias97 Date: Tue, 4 Feb 2025 22:45:54 +0100 Subject: [PATCH 006/317] Removendo notebook --- jupyter/test_apis.ipynb | 1252 --------------------------------------- 1 file changed, 1252 deletions(-) delete mode 100644 jupyter/test_apis.ipynb diff --git a/jupyter/test_apis.ipynb b/jupyter/test_apis.ipynb deleted file mode 100644 index 9815e8ba..00000000 --- a/jupyter/test_apis.ipynb +++ /dev/null @@ -1,1252 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 48, - "id": "initial_id", - "metadata": { - "ExecuteTime": { - "end_time": "2025-01-09T20:09:37.850465Z", - "start_time": "2025-01-09T20:09:35.741919Z" - }, - "collapsed": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[\u001B[34m2025-01-09T21:09:37.836+0100\u001B[0m] {\u001B[34m_client.py:\u001B[0m1025} INFO\u001B[0m - HTTP Request: GET https://estruturaorganizacional.dados.gov.br/doc/estrutura-organizacional/resumida?codigoPoder=1&codigoEsfera=1&codigoUnidade=7 \"HTTP/1.1 200 OK\"\u001B[0m\n" - ] - } - ], - "source": [ - "import sys\n", - "sys.path.append('../src')\n", - "\n", - "from airflow.clients.cliente_estrutura import ClienteEstrutura\n", - "client = ClienteEstrutura()\n", - "response = client.get_estrutura_organizacional_resumida(codigo_unidade='7', codigo_poder='1', codigo_esfera='1')" - ] - }, - { - "cell_type": "code", - "execution_count": 49, - "id": "11862578f62a2c86", - "metadata": { - "ExecuteTime": { - "end_time": "2025-01-09T20:09:38.644588Z", - "start_time": "2025-01-09T20:09:38.641016Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "90" - ] - }, - "execution_count": 49, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "len(response[\"unidades\"])" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "id": "604a2fc324e1fbdf", - "metadata": { - "ExecuteTime": { - "end_time": "2025-01-09T19:54:22.563588Z", - "start_time": "2025-01-09T19:54:22.303994Z" - } - }, - "outputs": [], - "source": [ - "from typing import Any, Dict, List\n", - "import json\n", - "\n", - "def flatten(data: Dict[str, Any], parent_key: str = \"\", sep: str = \"_\") -> Dict[str, Any]:\n", - " \"\"\"Flatten nested dictionaries.\n", - "\n", - " Args:\n", - " data: Dictionary to flatten\n", - " parent_key: Key from parent dictionary\n", - " sep: Separator between nested keys\n", - "\n", - " Returns:\n", - " Flattened dictionary\n", - " \"\"\"\n", - " items: List[tuple[str, Any]] = []\n", - " for key, value in data.items():\n", - " new_key = f\"{parent_key}{sep}{key}\" if parent_key else key\n", - "\n", - " if isinstance(value, dict):\n", - " items.extend(flatten(value, new_key, sep=sep).items())\n", - " elif isinstance(value, list):\n", - " items.append((new_key, json.dumps(value)))\n", - " else:\n", - " items.append((new_key, value))\n", - "\n", - " return dict(items)\n", - "\n", - "flattened_test = flatten(response, sep=\"_\")" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "id": "c01a90398d878fb7", - "metadata": { - "ExecuteTime": { - "end_time": "2025-01-09T19:55:46.261025Z", - "start_time": "2025-01-09T19:55:46.257853Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "str" - ] - }, - "execution_count": 41, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "type(flattened_test[\"unidades\"])" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "666607bcc9453bba", - "metadata": { - "ExecuteTime": { - "end_time": "2025-01-09T19:29:57.794235Z", - "start_time": "2025-01-09T19:29:57.783063Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'codigoUnidade': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/7', 'codigoUnidadePai': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/308804', 'codigoOrgaoEntidade': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/7', 'codigoTipoUnidade': 'https://estruturaorganizacional.dados.gov.br/id/tipo-unidade/entidade', 'nome': 'Instituto de Pesquisa Econômica Aplicada', 'sigla': 'IPEA', 'codigoEsfera': 'https://estruturaorganizacional.dados.gov.br/id/esfera/1', 'codigoPoder': 'https://estruturaorganizacional.dados.gov.br/id/poder/1', 'codigoNaturezaJuridica': 'https://estruturaorganizacional.dados.gov.br/id/natureza-juridica/2', 'codigoSubNaturezaJuridica': None, 'nivelNormatizacao': 'LEI_DECRETO', 'versaoConsulta': '114.0.0', 'dataInicialVersaoConsulta': '2024-10-08', 'dataFinalVersaoConsulta': None, 'operacao': None, 'codigoUnidadePaiAnterior': None, 'codigoOrgaoEntidadeAnterior': None}\n", - "{'codigoUnidade': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/7656', 'codigoUnidadePai': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/7', 'codigoOrgaoEntidade': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/7', 'codigoTipoUnidade': 'https://estruturaorganizacional.dados.gov.br/id/tipo-unidade/unidade-colegiada', 'nome': 'Conselho Consultivo', 'sigla': 'CC-IPEA', 'codigoEsfera': 'https://estruturaorganizacional.dados.gov.br/id/esfera/1', 'codigoPoder': 'https://estruturaorganizacional.dados.gov.br/id/poder/1', 'codigoNaturezaJuridica': 'https://estruturaorganizacional.dados.gov.br/id/natureza-juridica/2', 'codigoSubNaturezaJuridica': None, 'nivelNormatizacao': 'ATO_INTERNO', 'versaoConsulta': '107.0.191', 'dataInicialVersaoConsulta': '2021-09-24', 'dataFinalVersaoConsulta': None, 'operacao': None, 'codigoUnidadePaiAnterior': None, 'codigoOrgaoEntidadeAnterior': None}\n", - "{'codigoUnidade': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/7628', 'codigoUnidadePai': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/7', 'codigoOrgaoEntidade': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/7', 'codigoTipoUnidade': 'https://estruturaorganizacional.dados.gov.br/id/tipo-unidade/unidade-administrativa', 'nome': 'Gabinete', 'sigla': 'GABIN-IPEA', 'codigoEsfera': 'https://estruturaorganizacional.dados.gov.br/id/esfera/1', 'codigoPoder': 'https://estruturaorganizacional.dados.gov.br/id/poder/1', 'codigoNaturezaJuridica': 'https://estruturaorganizacional.dados.gov.br/id/natureza-juridica/2', 'codigoSubNaturezaJuridica': None, 'nivelNormatizacao': 'LEI_DECRETO', 'versaoConsulta': '113.1.0', 'dataInicialVersaoConsulta': '2024-10-01', 'dataFinalVersaoConsulta': None, 'operacao': None, 'codigoUnidadePaiAnterior': None, 'codigoOrgaoEntidadeAnterior': None}\n", - "{'codigoUnidade': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/255032', 'codigoUnidadePai': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/3561', 'codigoOrgaoEntidade': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/3561', 'codigoTipoUnidade': 'https://estruturaorganizacional.dados.gov.br/id/tipo-unidade/unidade-administrativa', 'nome': 'Diretoria de Políticas de Educação a Distância', 'sigla': 'DIPEAD', 'codigoEsfera': 'https://estruturaorganizacional.dados.gov.br/id/esfera/1', 'codigoPoder': 'https://estruturaorganizacional.dados.gov.br/id/poder/1', 'codigoNaturezaJuridica': 'https://estruturaorganizacional.dados.gov.br/id/natureza-juridica/4', 'codigoSubNaturezaJuridica': 'https://estruturaorganizacional.dados.gov.br/id/subnatureza-juridica/17', 'nivelNormatizacao': 'ATO_INTERNO', 'versaoConsulta': '112.1.0', 'dataInicialVersaoConsulta': '2022-05-25', 'dataFinalVersaoConsulta': None, 'operacao': None, 'codigoUnidadePaiAnterior': None, 'codigoOrgaoEntidadeAnterior': None}\n", - "{'codigoUnidade': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/105288', 'codigoUnidadePai': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/104862', 'codigoOrgaoEntidade': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/100930', 'codigoTipoUnidade': 'https://estruturaorganizacional.dados.gov.br/id/tipo-unidade/unidade-administrativa', 'nome': 'Diretoria de Pesquisa, Extensão e Assistência Estudantil', 'sigla': 'DIPEA', 'codigoEsfera': 'https://estruturaorganizacional.dados.gov.br/id/esfera/1', 'codigoPoder': 'https://estruturaorganizacional.dados.gov.br/id/poder/1', 'codigoNaturezaJuridica': 'https://estruturaorganizacional.dados.gov.br/id/natureza-juridica/4', 'codigoSubNaturezaJuridica': 'https://estruturaorganizacional.dados.gov.br/id/subnatureza-juridica/17', 'nivelNormatizacao': 'ATO_INTERNO', 'versaoConsulta': '114.1.0', 'dataInicialVersaoConsulta': '2022-08-08', 'dataFinalVersaoConsulta': None, 'operacao': None, 'codigoUnidadePaiAnterior': None, 'codigoOrgaoEntidadeAnterior': None}\n", - "{'codigoUnidade': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/440469', 'codigoUnidadePai': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/84156', 'codigoOrgaoEntidade': 'https://estruturaorganizacional.dados.gov.br/id/unidade-organizacional/41066', 'codigoTipoUnidade': 'https://estruturaorganizacional.dados.gov.br/id/tipo-unidade/unidade-administrativa', 'nome': 'Instituto de Pesquisas do Exército na Amazônia', 'sigla': 'IPEAM', 'codigoEsfera': 'https://estruturaorganizacional.dados.gov.br/id/esfera/1', 'codigoPoder': 'https://estruturaorganizacional.dados.gov.br/id/poder/1', 'codigoNaturezaJuridica': 'https://estruturaorganizacional.dados.gov.br/id/natureza-juridica/3', 'codigoSubNaturezaJuridica': None, 'nivelNormatizacao': 'ATO_INTERNO', 'versaoConsulta': '311.4.0', 'dataInicialVersaoConsulta': '2024-10-28', 'dataFinalVersaoConsulta': None, 'operacao': None, 'codigoUnidadePaiAnterior': None, 'codigoOrgaoEntidadeAnterior': None}\n" - ] - } - ], - "source": [ - "unidades = response['unidades']\n", - "\n", - "for unidade in unidades:\n", - " if 'IPEA' in unidade[\"sigla\"]:\n", - " print(unidade)" - ] - }, - { - "cell_type": "code", - "execution_count": 54, - "id": "57b91f5d0bf94795", - "metadata": { - "ExecuteTime": { - "end_time": "2025-01-09T20:10:27.352101Z", - "start_time": "2025-01-09T20:10:24.833263Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[\u001B[34m2025-01-09T21:10:27.343+0100\u001B[0m] {\u001B[34m_client.py:\u001B[0m1025} INFO\u001B[0m - HTTP Request: GET https://contratos.comprasnet.gov.br/api/contrato/ug/113602 \"HTTP/1.1 200 OK\"\u001B[0m\n" - ] - } - ], - "source": [ - "from airflow.clients.cliente_contratos import ClienteContratos\n", - "client = ClienteContratos()\n", - "response = client.get_contratos_by_ug(ug_code = '113602')" - ] - }, - { - "cell_type": "code", - "execution_count": 60, - "id": "5d226ed4294609f4", - "metadata": { - "ExecuteTime": { - "end_time": "2025-01-09T20:14:18.724625Z", - "start_time": "2025-01-09T20:14:18.720065Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "{'id': 4153,\n", - " 'receita_despesa': 'Despesa',\n", - " 'numero': '00014/2019',\n", - " 'contratante': {'orgao_origem': {'codigo': None,\n", - " 'nome': None,\n", - " 'unidade_gestora_origem': {'codigo': None,\n", - " 'nome_resumido': None,\n", - " 'nome': None,\n", - " 'sisg': 'Não',\n", - " 'utiliza_siafi': 'Não',\n", - " 'utiliza_antecipagov': 'Não'}},\n", - " 'orgao': {'codigo': '61201',\n", - " 'nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA',\n", - " 'unidade_gestora': {'codigo': '113602',\n", - " 'nome_resumido': 'IPEA/RJ',\n", - " 'nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA',\n", - " 'sisg': 'Sim',\n", - " 'utiliza_siafi': 'Sim',\n", - " 'utiliza_antecipagov': 'Sim'}}},\n", - " 'fornecedor': {'tipo': 'JURIDICA',\n", - " 'cnpj_cpf_idgener': '00.000.000/0001-91',\n", - " 'nome': 'BANCO DO BRASIL SA'},\n", - " 'codigo_tipo': '96',\n", - " 'tipo': 'Acordo de Cooperação Técnica (ACT)',\n", - " 'subtipo': None,\n", - " 'prorrogavel': None,\n", - " 'situacao': 'Ativo',\n", - " 'justificativa_inativo': None,\n", - " 'categoria': 'Serviços',\n", - " 'subcategoria': None,\n", - " 'unidades_requisitantes': None,\n", - " 'processo': '03001.002870/2019-10',\n", - " 'objeto': 'O PRESENTE INSTRUMENTO TEM POR OBJETIVO REGULAMENTAR O ESTABELECIMENTO, PELO BANCO, DOS CRITÉRIOS PARA ABERTURA DE DEPÓSITO EM GARANTIA - BLOQUEADO PARA MOVIMENTAÇÃO, DESTINADO A ABRIGAR OS RECURSOS PROVISIONADOS DE RUBRICAS CONSTANTES DA PLANILHA DE CUSTOS E FORMAÇÃO DE PREÇOS DOS CONTRATOS FIRMADOS PELA ADMINISTRAÇÃO PUBLICA FEDERAL, BEM COMO VIABILIZAR O ACESSO DA ADMINISTRAÇÃO PUBLICA FEDERAL AOS SALDOS E EXTRAÍAS DE TODOS OS \"EVENTOS\".',\n", - " 'amparo_legal': '',\n", - " 'informacao_complementar': None,\n", - " 'codigo_modalidade': 'NAOSEAPLIC',\n", - " 'modalidade': 'Não se Aplica',\n", - " 'unidade_compra': None,\n", - " 'licitacao_numero': None,\n", - " 'sistema_origem_licitacao': None,\n", - " 'data_assinatura': '2020-02-27',\n", - " 'data_publicacao': '2020-03-12',\n", - " 'data_proposta_comercial': None,\n", - " 'vigencia_inicio': '2020-02-27',\n", - " 'vigencia_fim': '2025-02-27',\n", - " 'valor_inicial': '0,01',\n", - " 'valor_global': '0,01',\n", - " 'num_parcelas': 1,\n", - " 'valor_parcela': '0,01',\n", - " 'valor_acumulado': '0,57',\n", - " 'links': {'historico': 'https://contratos.comprasnet.gov.br/api/contrato/4153/historico',\n", - " 'empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/4153/empenhos',\n", - " 'cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/4153/cronograma',\n", - " 'garantias': 'https://contratos.comprasnet.gov.br/api/contrato/4153/garantias',\n", - " 'itens': 'https://contratos.comprasnet.gov.br/api/contrato/4153/itens',\n", - " 'prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/4153/prepostos',\n", - " 'responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/4153/responsaveis',\n", - " 'despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/4153/despesas_acessorias',\n", - " 'faturas': 'https://contratos.comprasnet.gov.br/api/contrato/4153/faturas',\n", - " 'ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/4153/ocorrencias',\n", - " 'terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/4153/terceirizados',\n", - " 'arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/4153/arquivos'}}" - ] - }, - "execution_count": 60, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "response[0]" - ] - }, - { - "cell_type": "code", - "execution_count": 66, - "id": "adb07b2526a80a19", - "metadata": { - "ExecuteTime": { - "end_time": "2025-01-09T20:19:00.902262Z", - "start_time": "2025-01-09T20:19:00.894996Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[{'id': 4153, 'receita_despesa': 'Despesa', 'numero': '00014/2019', 'codigo_tipo': '96', 'tipo': 'Acordo de Cooperação Técnica (ACT)', 'subtipo': None, 'prorrogavel': None, 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.002870/2019-10', 'objeto': 'O PRESENTE INSTRUMENTO TEM POR OBJETIVO REGULAMENTAR O ESTABELECIMENTO, PELO BANCO, DOS CRITÉRIOS PARA ABERTURA DE DEPÓSITO EM GARANTIA - BLOQUEADO PARA MOVIMENTAÇÃO, DESTINADO A ABRIGAR OS RECURSOS PROVISIONADOS DE RUBRICAS CONSTANTES DA PLANILHA DE CUSTOS E FORMAÇÃO DE PREÇOS DOS CONTRATOS FIRMADOS PELA ADMINISTRAÇÃO PUBLICA FEDERAL, BEM COMO VIABILIZAR O ACESSO DA ADMINISTRAÇÃO PUBLICA FEDERAL AOS SALDOS E EXTRAÍAS DE TODOS OS \"EVENTOS\".', 'amparo_legal': '', 'informacao_complementar': None, 'codigo_modalidade': 'NAOSEAPLIC', 'modalidade': 'Não se Aplica', 'unidade_compra': None, 'licitacao_numero': None, 'sistema_origem_licitacao': None, 'data_assinatura': '2020-02-27', 'data_publicacao': '2020-03-12', 'data_proposta_comercial': None, 'vigencia_inicio': '2020-02-27', 'vigencia_fim': '2025-02-27', 'valor_inicial': '0,01', 'valor_global': '0,01', 'num_parcelas': 1, 'valor_parcela': '0,01', 'valor_acumulado': '0,57', 'contratante_orgao_origem_codigo': None, 'contratante_orgao_origem_nome': None, 'contratante_orgao_origem_unidade_gestora_origem_codigo': None, 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': None, 'contratante_orgao_origem_unidade_gestora_origem_nome': None, 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Não', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Não', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Não', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '00.000.000/0001-91', 'fornecedor_nome': 'BANCO DO BRASIL SA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/4153/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/4153/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/4153/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/4153/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/4153/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/4153/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/4153/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/4153/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/4153/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/4153/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/4153/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/4153/arquivos'}, {'id': 4980, 'receita_despesa': 'Despesa', 'numero': '00014/2019', 'codigo_tipo': '50', 'tipo': 'Contrato', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.003739/2019-70', 'objeto': 'PRESTAÇÃO DE SERVIÇOS DE TRANSPORTE TERRESTRE DE MATERIAIS E CARGAS, CONFORME CONDIÇÕES ESTABELECIDAS NO TERMO DE REFERÊNCIA E EDITAL.', 'amparo_legal': '', 'informacao_complementar': 'ESTE TERMO DE CONTRATO VINCULA-SE AO EDITAL DO PREGÃO N°08/2018 - ATA SRP N°67/2018 - UASG: 443020 - INSTITUTO DE PESQUISAS JARDIM BOTÂNICO - RIO DE JANEIRO, IDENTIFICADO NO PREÂMBULO E À PROPOSTA VENCEDORA, INDEPENDENTEMENTE DE TRANSCRIÇÃO.', 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '443020', 'licitacao_numero': '00008/2018', 'sistema_origem_licitacao': None, 'data_assinatura': '2019-12-03', 'data_publicacao': '2019-12-05', 'data_proposta_comercial': None, 'vigencia_inicio': '2019-12-03', 'vigencia_fim': '2021-12-03', 'valor_inicial': '555.680,00', 'valor_global': '555.680,00', 'num_parcelas': 12, 'valor_parcela': '46.306,67', 'valor_acumulado': '1.071.711,74', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '01.117.975/0001-67', 'fornecedor_nome': 'MAXPESA CONSTRUCOES TRANSPORTES LOCACOES E MONTAGENS LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/4980/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/4980/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/4980/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/4980/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/4980/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/4980/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/4980/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/4980/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/4980/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/4980/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/4980/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/4980/arquivos'}, {'id': 5738, 'receita_despesa': 'Despesa', 'numero': '00001/2020', 'codigo_tipo': '50', 'tipo': 'Contrato', 'subtipo': None, 'prorrogavel': None, 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.000485/2020-71', 'objeto': 'CONTRATAÇÃO DE EMPRESA ESPECIALIZADA PARA O FORNECIMENTO, TRANSPORTE E INSTALAÇÃO DO FORRO MINERAL PARA A UNIDADE DO IPEA/RJ.', 'amparo_legal': '', 'informacao_complementar': 'ESTE CONTRATO É ORIUNDO DA ATA DE REGISTRO DE PREÇOS N.º 072/2019, DERIVADA DO PREGÃO ELETRÔNICO Nº 58/2019, COM O CORRESPONDENTE EDITAL DE LICITAÇÃO E COM A PROPOSTA DA CONTRATADA BERNARDO DE SÁ CONSTRUTORA, INCORPORADORA E EMPREENDIMENTOS IMOBILIÁRIOS LTDA, INSCRITA NO CNPJ N° 09.248.466/0001-85, QUE INDEPENDENTEMENTE DE TRANSCRIÇÃO PASSAM A FAZER PARTE INTEGRANTE E COMPLEMENTAR DO PRESENTE CONTRATO.', 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '080016', 'licitacao_numero': '00072/2019', 'sistema_origem_licitacao': None, 'data_assinatura': '2020-02-27', 'data_publicacao': '2020-03-02', 'data_proposta_comercial': None, 'vigencia_inicio': '2020-02-27', 'vigencia_fim': '2020-06-03', 'valor_inicial': '118.000,00', 'valor_global': '472.000,00', 'num_parcelas': 1, 'valor_parcela': '472.000,00', 'valor_acumulado': '472.000,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '09.248.466/0001-85', 'fornecedor_nome': 'CASA BLANKA CONSTRUTORA E INCORPORADORA LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/5738/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/5738/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/5738/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/5738/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/5738/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/5738/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/5738/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/5738/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/5738/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/5738/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/5738/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/5738/arquivos'}, {'id': 6446, 'receita_despesa': 'Despesa', 'numero': '51257/2019', 'codigo_tipo': '98', 'tipo': 'Outros', 'subtipo': 'TERMO DE CESSÃO DE USO DE ÁREA', 'prorrogavel': None, 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Cessão', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.003714/2019-76', 'objeto': 'TERMO DE CESSÃO DE USO DE ÁREA DISPONÍVEL NO EDIFÍCIO LOCALIZADO NA AV. PRESIDENTE VARGAS, Nº 730, NO RIO DE JANEIRO/RJ, QUE ENTRE SI FAZEM O BANCO CENTRAL DO BRASIL, COMO CEDENTE, E O INSTITUTO DE PESQUISA ECONÔMICA APLICADA, COMO CESSIONÁRIA.', 'amparo_legal': '', 'informacao_complementar': 'TERMO DE CESSÃO DE USO', 'codigo_modalidade': 'NAOSEAPLIC', 'modalidade': 'Não se Aplica', 'unidade_compra': None, 'licitacao_numero': None, 'sistema_origem_licitacao': None, 'data_assinatura': '2019-12-16', 'data_publicacao': '2020-12-17', 'data_proposta_comercial': None, 'vigencia_inicio': '2020-01-02', 'vigencia_fim': '2022-07-01', 'valor_inicial': '0,01', 'valor_global': '0,01', 'num_parcelas': 30, 'valor_parcela': '0,01', 'valor_acumulado': '0,30', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '00.038.166/0010-98', 'fornecedor_nome': 'BANCO CENTRAL DO BRASIL', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/6446/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/6446/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/6446/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/6446/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/6446/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/6446/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/6446/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/6446/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/6446/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/6446/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/6446/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/6446/arquivos'}, {'id': 64894, 'receita_despesa': 'Despesa', 'numero': '2020NE800068', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': None, 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.000701/2020-89', 'objeto': 'CONTRATAÇÃO DE SERVIÇO DE EMPRESA DE ENGENHARIA ESPECIALIZADA NA FABRICAÇÃO E INSTALAÇÃO DE REDE DE DUTOS PARA O CPD DO NOVO ENDEREÇO DO IPEA - UNIDADE DESCENTRALIZADA NO RIO DE JANEIRO/RJ, SITUADO NO EDIFÍCIO DO BANCO CENTRAL .', 'amparo_legal': '', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00004/2020', 'sistema_origem_licitacao': None, 'data_assinatura': '2020-03-10', 'data_publicacao': '2020-04-15', 'data_proposta_comercial': None, 'vigencia_inicio': '2020-03-11', 'vigencia_fim': '2020-05-27', 'valor_inicial': '13.900,00', 'valor_global': '13.900,00', 'num_parcelas': 1, 'valor_parcela': '13.900,00', 'valor_acumulado': '13.900,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '26.669.875/0001-74', 'fornecedor_nome': 'CVAS REFRIGERACAO LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/64894/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/64894/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/64894/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/64894/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/64894/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/64894/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/64894/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/64894/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/64894/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/64894/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/64894/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/64894/arquivos'}, {'id': 64895, 'receita_despesa': 'Despesa', 'numero': '2020NE800121', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': None, 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.001956/2020-69', 'objeto': 'AQUISIÇÃO DE 02 (DUAS) ASSINATURAS DIGITAIS ANUAIS DA REVISTA “CONJUNTURA ECONÔMICA”.', 'amparo_legal': '', 'informacao_complementar': None, 'codigo_modalidade': '07', 'modalidade': 'Inexigibilidade', 'unidade_compra': '113602', 'licitacao_numero': '00003/2020', 'sistema_origem_licitacao': None, 'data_assinatura': '2020-06-29', 'data_publicacao': None, 'data_proposta_comercial': None, 'vigencia_inicio': '2020-07-03', 'vigencia_fim': '2021-07-03', 'valor_inicial': '230,00', 'valor_global': '230,00', 'num_parcelas': 1, 'valor_parcela': '230,00', 'valor_acumulado': '230,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '33.641.663/0001-44', 'fornecedor_nome': 'FUNDACAO GETULIO VARGAS', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/64895/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/64895/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/64895/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/64895/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/64895/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/64895/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/64895/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/64895/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/64895/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/64895/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/64895/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/64895/arquivos'}, {'id': 64902, 'receita_despesa': 'Despesa', 'numero': '2020NE800026', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': None, 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.004691/2019-17', 'objeto': 'AQUISIÇÃO DE EQUIPAMENTOS DE CONTROLE DE ACESSO BIOMÉTRICO E DE SISTEMA DE VÍDEO VIGILÂNCIA POR CIRCUITO INTERNO DE TV (CFTV) PARA A INFRAESTRUTUTA DA NOVA SEDE DO IPEA/RJ.', 'amparo_legal': '', 'informacao_complementar': 'ADESÃO À ATA DO PREGÃO 09/2018 DA UASG 160530', 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '113602', 'licitacao_numero': '00009/2018', 'sistema_origem_licitacao': None, 'data_assinatura': '2020-01-30', 'data_publicacao': '2021-04-14', 'data_proposta_comercial': None, 'vigencia_inicio': '2020-02-06', 'vigencia_fim': '2020-12-07', 'valor_inicial': '26.738,70', 'valor_global': '26.737,14', 'num_parcelas': 1, 'valor_parcela': '26.737,14', 'valor_acumulado': '26.737,14', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '82.901.000/0001-27', 'fornecedor_nome': 'INTELBRAS S.A. INDUSTRIA DE TELECOMUNICACAO ELETRONICA BRASILEIRA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/64902/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/64902/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/64902/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/64902/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/64902/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/64902/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/64902/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/64902/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/64902/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/64902/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/64902/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/64902/arquivos'}, {'id': 88309, 'receita_despesa': 'Despesa', 'numero': '00001/2021', 'codigo_tipo': '50', 'tipo': 'Contrato', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.002352/2020-30', 'objeto': 'CONTRATAÇÃO DE PRODUTOS E SERVIÇOS POR MEIO DE PACOTE\\r\\nDE SERVIÇOS DOS CORREIOS', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 25 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '07', 'modalidade': 'Inexigibilidade', 'unidade_compra': '113602', 'licitacao_numero': '00002/2021', 'sistema_origem_licitacao': None, 'data_assinatura': '2021-01-21', 'data_publicacao': '2021-02-08', 'data_proposta_comercial': None, 'vigencia_inicio': '2021-01-21', 'vigencia_fim': '2026-01-21', 'valor_inicial': '1.718,32', 'valor_global': '1.718,32', 'num_parcelas': 1, 'valor_parcela': '1.718,32', 'valor_acumulado': '1.718,32', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '34.028.316/0002-94', 'fornecedor_nome': 'EMPRESA BRASILEIRA DE CORREIOS E TELEGRAFOS', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/88309/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/88309/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/88309/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/88309/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/88309/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/88309/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/88309/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/88309/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/88309/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/88309/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/88309/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/88309/arquivos'}, {'id': 101463, 'receita_despesa': 'Despesa', 'numero': '2021NE000022', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': None, 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.001229/2021-82', 'objeto': 'RENOVAÇÃO DE CERTIFICADO DIGITAL TOKEN,PARA O CHEFE DO SERVIÇO DE EXECUÇÃO ORÇAMENTÁRIA E FINANCEIRA (SEEOF)-SERVIDOR\\r\\nJORGE ACÁCIO DE A.SILVA.', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 25', 'informacao_complementar': None, 'codigo_modalidade': '07', 'modalidade': 'Inexigibilidade', 'unidade_compra': None, 'licitacao_numero': '00004/2021', 'sistema_origem_licitacao': None, 'data_assinatura': '2021-04-20', 'data_publicacao': '2021-04-30', 'data_proposta_comercial': None, 'vigencia_inicio': '2021-05-04', 'vigencia_fim': '2021-05-07', 'valor_inicial': '0,00', 'valor_global': '206,00', 'num_parcelas': 1, 'valor_parcela': '206,00', 'valor_acumulado': '206,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'UG', 'fornecedor_cnpj_cpf_idgener': '806030', 'fornecedor_nome': 'SERPRO - SEDE - BRASILIA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/101463/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/101463/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/101463/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/101463/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/101463/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/101463/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/101463/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/101463/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/101463/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/101463/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/101463/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/101463/arquivos'}, {'id': 120371, 'receita_despesa': 'Despesa', 'numero': '00003/2021', 'codigo_tipo': '50', 'tipo': 'Contrato', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.000089/2021-25', 'objeto': 'CONTRATAÇÃO DE SERVIÇOS CONTINUADOS DE OUTSOURCING PARA OPERAÇÃO DE ALMOXARIFADO VIRTUAL, SOB DEMANDA, VISANDO AO SUPRIMENTO DE MATERIAIS DE CONSUMO, VIA SISTEMA WEB', 'amparo_legal': 'LEI 10.520 / 2002 - Artigo: 1', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '201057', 'licitacao_numero': '00007/2020', 'sistema_origem_licitacao': None, 'data_assinatura': '2021-10-21', 'data_publicacao': '2021-11-04', 'data_proposta_comercial': None, 'vigencia_inicio': '2021-10-21', 'vigencia_fim': '2024-04-21', 'valor_inicial': '146.539,03', 'valor_global': '163.540,28', 'num_parcelas': 1, 'valor_parcela': '163.540,28', 'valor_acumulado': '6.613.212,86', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '06.698.091/0005-90', 'fornecedor_nome': 'AUTOPEL AUTOMACAO COMERCIAL E INFORMATICA LTDA.', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/120371/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/120371/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/120371/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/120371/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/120371/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/120371/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/120371/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/120371/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/120371/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/120371/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/120371/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/120371/arquivos'}, {'id': 121592, 'receita_despesa': 'Despesa', 'numero': '00014/2019', 'codigo_tipo': '96', 'tipo': 'Acordo de Cooperação Técnica (ACT)', 'subtipo': None, 'prorrogavel': None, 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.002870/2019-10', 'objeto': 'REGULAMENTAR O ESTABELECIMENTO, PELO\\r\\nBANCO, DOS CRITÉRIOS PARA ABERTURA DE DEPÓSITO EM GARANTIA - BLOQUEADO PARA\\r\\nMOVIMENTAÇÃO, DESTINADO A ABRIGAR OS RECURSOS PROVISIONADOS DE RUBRICAS CONSTANTES DA PLANILHA DE CUSTOS E FORMAÇÃO DE PREÇOS DOS CONTRATOS FIRMADOS PELA ADMINISTRAÇÃO PUBLICA FEDERAL, BEM COMO VIABILIZAR O ACESSO DA ADMINISTRAÇÃO PUBLICA\\r\\nFEDERAL AOS SALDOS E EXTRAÍAS DE TODOS OS \"EVENTOS\".', 'amparo_legal': 'NÃO SE APLICA', 'informacao_complementar': None, 'codigo_modalidade': 'NAOSEAPLIC', 'modalidade': 'Não se Aplica', 'unidade_compra': None, 'licitacao_numero': None, 'sistema_origem_licitacao': None, 'data_assinatura': '2020-02-27', 'data_publicacao': '2021-11-12', 'data_proposta_comercial': None, 'vigencia_inicio': '2020-02-27', 'vigencia_fim': '2025-02-27', 'valor_inicial': '0,00', 'valor_global': '0,00', 'num_parcelas': 1, 'valor_parcela': '0,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '00.000.000/0001-91', 'fornecedor_nome': 'BANCO DO BRASIL SA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/121592/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/121592/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/121592/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/121592/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/121592/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/121592/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/121592/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/121592/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/121592/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/121592/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/121592/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/121592/arquivos'}, {'id': 131347, 'receita_despesa': 'Despesa', 'numero': '2021NE000045', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.002717/2021-15', 'objeto': 'PRODUTOS ALIMENTÍCIOS - ADOÇANTES 45 FRASCOS', 'amparo_legal': 'LEI 10.520 / 2002 - Artigo: 1', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '113602', 'licitacao_numero': '00001/2021', 'sistema_origem_licitacao': None, 'data_assinatura': '2021-11-30', 'data_publicacao': '2021-11-30', 'data_proposta_comercial': None, 'vigencia_inicio': '2021-11-30', 'vigencia_fim': '2021-12-01', 'valor_inicial': '3.263,50', 'valor_global': '3.263,50', 'num_parcelas': 1, 'valor_parcela': '3.263,50', 'valor_acumulado': '3.263,50', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '22.243.972/0001-40', 'fornecedor_nome': 'AGO MOVEIS E SERVICOS LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/131347/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/131347/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/131347/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/131347/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/131347/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/131347/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/131347/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/131347/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/131347/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/131347/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/131347/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/131347/arquivos'}, {'id': 138385, 'receita_despesa': 'Despesa', 'numero': '2022NE000001', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.003150/2021-96', 'objeto': 'RENOVAÇÃO DE (01) ASSINATURA EM VERSÃO DIGITAL, DA REVISTA THE ECONOMIST, PELO PERÍODO DE 12 MESES, REVISTA QUE INFORMA E OFERECE ANÁLISE DE NOTÍCIAS INTERNACIONAIS, POLÍTICA MUNDIAL, NEGÓCIOS, FINANÇAS, CIÊNCIA E TECNOLOGIA, COM REPUTAÇÃO DE FONTE DE INFORMAÇÃO INTELIGENTE E CONFIÁVEL QUE É IMPORTANTE PARA SUBSIDIAR PESQUISAS REALIZADAS NO INSTITUTO.', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 25', 'informacao_complementar': None, 'codigo_modalidade': '07', 'modalidade': 'Inexigibilidade', 'unidade_compra': '113602', 'licitacao_numero': '00001/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2022-01-26', 'data_publicacao': '2022-01-26', 'data_proposta_comercial': None, 'vigencia_inicio': '2022-02-26', 'vigencia_fim': '2023-02-26', 'valor_inicial': '1.165,06', 'valor_global': '1.165,06', 'num_parcelas': 1, 'valor_parcela': '1.165,06', 'valor_acumulado': '1.165,06', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '07.492.558/0001-80', 'fornecedor_nome': 'MEDIA INTERNATIONAL DISTRIBUICAO DE PUBLICACOES LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/138385/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/138385/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/138385/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/138385/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/138385/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/138385/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/138385/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/138385/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/138385/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/138385/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/138385/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/138385/arquivos'}, {'id': 141853, 'receita_despesa': 'Despesa', 'numero': '00001/2022', 'codigo_tipo': '50', 'tipo': 'Contrato', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.002349/2021-05', 'objeto': 'CONTRATAÇÃO DE PRODUTOS E SERVIÇOS POR MEIO DE PACOTE DE SERVIÇOS DOS CORREIOS MEDIANTE ADESÃO AO TERMO DE CONDIÇÕES COMERCIAIS E ANEXOS, QUANDO CONTRATADOS SERVIÇOS ESPECÍFICOS, QUE PERMITE A COMPRA DE PRODUTOS E UTILIZAÇÃO DOS DIVERSOS SERVIÇOS DOS CORREIOS POR MEIO DOS CANAIS DE ATENDIMENTO DISPONIBILIZADOS.', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: III', 'informacao_complementar': 'SERVIÇOS POSTAIS NÃO COMPREENDIDOS NO MONOPÓLIO ESTATAL DOS CORREIOS (SEDEX/PAC)', 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00001/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2022-03-31', 'data_publicacao': '2023-05-30', 'data_proposta_comercial': None, 'vigencia_inicio': '2022-03-31', 'vigencia_fim': '2025-03-31', 'valor_inicial': '4.493,81', 'valor_global': '4.691,09', 'num_parcelas': 12, 'valor_parcela': '390,92', 'valor_acumulado': '13.678,56', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '34.028.316/0002-94', 'fornecedor_nome': 'EMPRESA BRASILEIRA DE CORREIOS E TELEGRAFOS', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/141853/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/141853/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/141853/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/141853/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/141853/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/141853/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/141853/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/141853/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/141853/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/141853/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/141853/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/141853/arquivos'}, {'id': 148548, 'receita_despesa': 'Despesa', 'numero': '2022NE000024', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.000950/2022-36', 'objeto': 'CONTRATAÇÃO DE SERVIÇO DE ACESSO A UM BANCO DE PREÇOS PÚBLICOS POR MEIO DE SENHA PESSOAL PARA USO DO SEACC- IPEA/RJ E DO SETOR DE COMPRAS DO IPEA/BSB', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00005/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2022-05-25', 'data_publicacao': '2022-05-26', 'data_proposta_comercial': None, 'vigencia_inicio': '2022-05-25', 'vigencia_fim': '2023-05-25', 'valor_inicial': '8.500,00', 'valor_global': '8.500,00', 'num_parcelas': 1, 'valor_parcela': '8.500,00', 'valor_acumulado': '8.500,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '16.538.909/0001-38', 'fornecedor_nome': 'PROMAXIMA GESTAO EMPRESARIAL LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/148548/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/148548/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/148548/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/148548/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/148548/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/148548/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/148548/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/148548/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/148548/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/148548/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/148548/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/148548/arquivos'}, {'id': 156286, 'receita_despesa': 'Despesa', 'numero': '2022NE000019', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.000486/2022-88', 'objeto': 'PRESTAÇÃO DE SERVIÇOS DE ENGENHARIA PARA LIMPEZA ROBOTIZADA, POR ESCOVAÇÃO A SECO COM FILMAGEM SIMULTÂNEA, DE REDE DE DUTOS E DESCONTAMINAÇÃO DE SISTEMA DEAR CONDICIONADO E VENTILAÇÃO DAS INSTALAÇÕES DO CPD DO IPEA/RJ, \\r\\n LOCALIZADO NA AV. PRESIDENTE VARGAS, Nº730/16° ANDAR NO CENTRO DO RIO DE JANEIRO. OS SERVIÇOS COMPREENDEM LIMPEZA E DESCONTAMINAÇÃO 01(UMA) VEZ AO ANO E EMISSÃO DE LAUDOS COM AVALIAÇÃO DA QUALIDADE DO AR 02 (DUAS) VEZES AO\\r\\nANO, A CADA 06 (SEIS) MESES.', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: I', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00004/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2022-04-08', 'data_publicacao': '2022-04-09', 'data_proposta_comercial': None, 'vigencia_inicio': '2022-04-08', 'vigencia_fim': '2023-01-30', 'valor_inicial': '1.490,00', 'valor_global': '1.490,00', 'num_parcelas': 1, 'valor_parcela': '1.490,00', 'valor_acumulado': '1.490,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '31.316.334/0001-00', 'fornecedor_nome': 'REFRIMEC REFRIGERACAO LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/156286/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/156286/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/156286/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/156286/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/156286/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/156286/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/156286/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/156286/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/156286/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/156286/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/156286/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/156286/arquivos'}, {'id': 166569, 'receita_despesa': 'Despesa', 'numero': '20220/0003', 'codigo_tipo': '98', 'tipo': 'Outros', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.002692/2022-22', 'objeto': 'PARAFUSADEIRA/FURADEIRA À BATERIA 20V BIVOLT 1/2\" 1500RPM PORTÁTIL - VELOCIDAD E VARIÁVEL E REVERSÍVEL - MANDRIL DE APERTO RÁPIDO - UM CARREGADOR E DUAS BAT ERIAS - ITEM DE REFERÊNCIA: DEWALT DCD776C2- BR', 'amparo_legal': 'LEI 10.520 / 2002 - Artigo: 1', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '113602', 'licitacao_numero': '00077/2021', 'sistema_origem_licitacao': None, 'data_assinatura': '2022-10-18', 'data_publicacao': '2022-10-19', 'data_proposta_comercial': None, 'vigencia_inicio': '2022-10-18', 'vigencia_fim': '2022-11-07', 'valor_inicial': '940,00', 'valor_global': '940,00', 'num_parcelas': 1, 'valor_parcela': '940,00', 'valor_acumulado': '940,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '39.877.684/0001-40', 'fornecedor_nome': 'COMERCIAL AVAN LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/166569/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/166569/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/166569/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/166569/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/166569/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/166569/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/166569/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/166569/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/166569/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/166569/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/166569/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/166569/arquivos'}, {'id': 170246, 'receita_despesa': 'Despesa', 'numero': '00000/0000', 'codigo_tipo': '98', 'tipo': 'Outros', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.002692/2022-22', 'objeto': '333903016 - MATERIAL DE EXPEDIENTE', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '113602', 'licitacao_numero': '00009/2021', 'sistema_origem_licitacao': None, 'data_assinatura': '2022-10-18', 'data_publicacao': '2022-10-20', 'data_proposta_comercial': None, 'vigencia_inicio': '2022-10-18', 'vigencia_fim': '2022-12-31', 'valor_inicial': '1.390,60', 'valor_global': '1.390,60', 'num_parcelas': 1, 'valor_parcela': '1.390,60', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '26.854.929/0001-71', 'fornecedor_nome': 'DIDAQUE EMPREENDIMENTOS LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/170246/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/170246/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/170246/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/170246/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/170246/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/170246/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/170246/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/170246/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/170246/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/170246/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/170246/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/170246/arquivos'}, {'id': 175309, 'receita_despesa': 'Despesa', 'numero': '2022NE000048', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SELOPE E SEINF', 'processo': '03001.002707/2022-52', 'objeto': 'AQUISIÇÃO PARA ATENDER A DEMANDA POR MATERIAL DE CONSUMO DE APOIO AS ATIVIDADES ROTINEIRAS DA GERIO DO IPEA-INSTITUTO DE PESQUISA ECONOMICA APLICADA NO RIO DE JANEIRO, CONFORME CONDIÇÕES, QUANTIDADES E EXIGÊNCIAS ESTABELECIDAS NO TERMO DE REFERÊNCIA (SEI 0485055)- ITENS 14 E 15.', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: I', 'informacao_complementar': 'VISA DAR SUPORTE PARA MELHORIA NAS AÇÕES E MANUTENÇÃO DO ATENDIMENTO DAS ATIVIDADES DESEMPENHADAS PELAS ÁREAS DEMANDANTES SELOP, SEINF NO IPEA-RJ.', 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00016/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2022-12-06', 'data_publicacao': '2022-12-27', 'data_proposta_comercial': None, 'vigencia_inicio': '2022-12-06', 'vigencia_fim': '2022-12-31', 'valor_inicial': '527,30', 'valor_global': '527,30', 'num_parcelas': 1, 'valor_parcela': '527,30', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '38.485.259/0001-42', 'fornecedor_nome': 'MAKTUB DISTRIBUIDORA LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/175309/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/175309/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/175309/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/175309/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/175309/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/175309/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/175309/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/175309/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/175309/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/175309/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/175309/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/175309/arquivos'}, {'id': 175628, 'receita_despesa': 'Despesa', 'numero': '2022NE000044', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.002707/2022-52', 'objeto': 'AQUISIÇÃO DE 02 FORNOS DE MICRO-ONDAS E 01 REFRIGERADOR FRIGOBAR PARA SUPORTE E MELHORIA NAS AÇÕES E MANUTENÇÃO DO ATENDIMENTO DAS ATIVIDADES DESEMPENHADAS PELAS ÁREAS DEMANTES NO IPEA-RJ', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00011/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2022-11-22', 'data_publicacao': '2022-12-29', 'data_proposta_comercial': None, 'vigencia_inicio': '2022-11-22', 'vigencia_fim': '2022-12-31', 'valor_inicial': '3.915,80', 'valor_global': '3.915,80', 'num_parcelas': 1, 'valor_parcela': '3.915,80', 'valor_acumulado': '3.915,80', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '37.502.052/0001-76', 'fornecedor_nome': 'OBEN COMERCIAL LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/175628/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/175628/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/175628/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/175628/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/175628/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/175628/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/175628/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/175628/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/175628/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/175628/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/175628/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/175628/arquivos'}, {'id': 180914, 'receita_despesa': 'Despesa', 'numero': '2022NE000045', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.002707/2022-52', 'objeto': 'AQUISIÇÃO DE 03 (TRÊS) CAFETEIRAS INDUSTRIAIS PARA ATENDIMENTO AO IPEA/RJ.', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00012/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2022-11-22', 'data_publicacao': '2023-02-01', 'data_proposta_comercial': '2022-09-22', 'vigencia_inicio': '2022-11-22', 'vigencia_fim': '2023-01-31', 'valor_inicial': '6.210,00', 'valor_global': '6.210,00', 'num_parcelas': 1, 'valor_parcela': '6.210,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '11.094.173/0001-32', 'fornecedor_nome': 'OFFICE DO BRASIL IMPORTACAO E EXPORTACAO LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/180914/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/180914/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/180914/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/180914/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/180914/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/180914/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/180914/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/180914/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/180914/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/180914/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/180914/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/180914/arquivos'}, {'id': 181487, 'receita_despesa': 'Despesa', 'numero': '2022NE000046', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.002912/2022-18', 'objeto': 'SERVIÇO DE CONFECÇÃO DE CORDÕES PARA CRACHÁS A SEREM UTILIZADOS NAS DEPENDÊNCIAS DO IPEA NO ED. BACEN.', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00014/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2022-11-21', 'data_publicacao': '2023-02-07', 'data_proposta_comercial': None, 'vigencia_inicio': '2022-11-21', 'vigencia_fim': '2023-02-15', 'valor_inicial': '4.120,00', 'valor_global': '4.120,00', 'num_parcelas': 1, 'valor_parcela': '4.120,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '38.458.393/0001-54', 'fornecedor_nome': 'EXXPRESS BRINDES LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/181487/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/181487/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/181487/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/181487/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/181487/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/181487/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/181487/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/181487/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/181487/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/181487/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/181487/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/181487/arquivos'}, {'id': 183180, 'receita_despesa': 'Despesa', 'numero': '2023NE000015', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'Biblioteca', 'processo': '03001.003730/2022-64', 'objeto': 'RENOVAÇÃO DE 01 ASSINATURA DIGITAL DA REVISTA THE ECONOMIST PELO PERÍODO DE 12 MESES.', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 74 - Inciso: CAPUT', 'informacao_complementar': None, 'codigo_modalidade': '07', 'modalidade': 'Inexigibilidade', 'unidade_compra': '113602', 'licitacao_numero': '00002/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-02-15', 'data_publicacao': '2023-02-24', 'data_proposta_comercial': None, 'vigencia_inicio': '2023-02-27', 'vigencia_fim': '2023-12-31', 'valor_inicial': '1.074,00', 'valor_global': '1.074,00', 'num_parcelas': 1, 'valor_parcela': '1.074,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '07.492.558/0001-80', 'fornecedor_nome': 'MEDIA INTERNATIONAL DISTRIBUICAO DE PUBLICACOES LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/183180/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/183180/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/183180/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/183180/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/183180/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/183180/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/183180/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/183180/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/183180/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/183180/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/183180/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/183180/arquivos'}, {'id': 183517, 'receita_despesa': 'Despesa', 'numero': '00002/2023', 'codigo_tipo': '50', 'tipo': 'Contrato', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.003357/2022-41', 'objeto': 'CONTRATAÇÃO DO LICENCIAMENTO DE CONTEÚDOS NOTICIOSOS BROADCAST, INCLUINDO OS PACOTES DO BROADCAST NEWS + O PACOTE ADD ON AGRO.', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 74 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '07', 'modalidade': 'Inexigibilidade', 'unidade_compra': '113602', 'licitacao_numero': '00001/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-02-27', 'data_publicacao': '2023-03-03', 'data_proposta_comercial': '2023-01-12', 'vigencia_inicio': '2023-03-02', 'vigencia_fim': '2025-03-02', 'valor_inicial': '44.217,30', 'valor_global': '46.211,56', 'num_parcelas': 24, 'valor_parcela': '1.925,48', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '62.652.961/0001-38', 'fornecedor_nome': 'AGENCIA ESTADO S.A', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/183517/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/183517/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/183517/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/183517/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/183517/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/183517/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/183517/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/183517/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/183517/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/183517/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/183517/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/183517/arquivos'}, {'id': 183520, 'receita_despesa': 'Despesa', 'numero': '00001/2023', 'codigo_tipo': '50', 'tipo': 'Contrato', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'SEINF', 'processo': '03001.003954/2022-76', 'objeto': 'CONTRATAÇÃO CONJUNTA DE PRESTAÇÃO DE SERVIÇO MÓVEL PESSOAL (SMP - DADOS MÓVEIS E VOZ), GESTÃO DE DISPOSITIVOS MÓVEIS (MDM) E OPÇÃO POR APARELHOS MÓVEIS EM COMODATO, CONFORME ESPECIFICAÇÕES E CONDIÇÕES CONSTANTES NO TERMO DE REFERÊNCIA.', 'amparo_legal': 'LEI 10.520 / 2002 - Artigo: 1', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '201057', 'licitacao_numero': '00013/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-03-01', 'data_publicacao': '2023-03-02', 'data_proposta_comercial': '2022-11-17', 'vigencia_inicio': '2023-03-01', 'vigencia_fim': '2025-09-01', 'valor_inicial': '9.100,80', 'valor_global': '9.340,20', 'num_parcelas': 1, 'valor_parcela': '9.340,20', 'valor_acumulado': '149.443,20', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '40.432.544/0001-47', 'fornecedor_nome': 'CLARO S.A.', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/183520/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/183520/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/183520/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/183520/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/183520/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/183520/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/183520/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/183520/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/183520/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/183520/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/183520/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/183520/arquivos'}, {'id': 183611, 'receita_despesa': 'Despesa', 'numero': '2022NE000049', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SELOPE E SEINF', 'processo': '03001.002707/2022-52', 'objeto': 'AQUISIÇÃO DE MATERIAL DE CONSUMO PARA ATENDER A DEMANDA DAS ATIVIDADES ROTINEIRAS E DE APOIO DA GERIO DO IPEA-RJ', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00015/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2022-12-06', 'data_publicacao': '2023-03-01', 'data_proposta_comercial': '2022-11-09', 'vigencia_inicio': '2022-12-08', 'vigencia_fim': '2023-02-28', 'valor_inicial': '1.264,22', 'valor_global': '1.264,22', 'num_parcelas': 1, 'valor_parcela': '1.264,22', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '46.814.710/0001-56', 'fornecedor_nome': 'LIDIANE DE SOUZA SAIOL PONTES 09944529770', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/183611/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/183611/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/183611/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/183611/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/183611/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/183611/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/183611/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/183611/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/183611/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/183611/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/183611/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/183611/arquivos'}, {'id': 184872, 'receita_despesa': 'Despesa', 'numero': '2022NE000043', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.002707/2022-52', 'objeto': 'AQUISIÇÃO DE APONTADOR DE LÁPIS ELÉTRICO', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00010/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2022-11-21', 'data_publicacao': '2023-03-13', 'data_proposta_comercial': None, 'vigencia_inicio': '2022-11-21', 'vigencia_fim': '2023-03-31', 'valor_inicial': '106,50', 'valor_global': '106,50', 'num_parcelas': 1, 'valor_parcela': '106,50', 'valor_acumulado': '106,50', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '46.814.710/0001-56', 'fornecedor_nome': 'LIDIANE DE SOUZA SAIOL PONTES 09944529770', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/184872/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/184872/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/184872/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/184872/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/184872/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/184872/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/184872/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/184872/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/184872/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/184872/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/184872/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/184872/arquivos'}, {'id': 187512, 'receita_despesa': 'Despesa', 'numero': '2023NE000032', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.000001/2023-37', 'objeto': 'AQUISIÇÃO DE 06 SPLITS DE AR CONDICIONADO DE 12.000 BTUS, VISANDO PROPICIAR, DURANTE A NOITE, FINS DE SEMANA E FERIADOS, MELHOR CLIMATIZAÇÃO DAS SALAS DOS RACKS QUE ACONDICIONAM OS SWITCHES DE REDE, LOCALIZADOS NO 17º ANDAR - (SALA 1720), 18º ANDAR - (SALA 1819) E 19º ANDAR - (SALA 1911) DO IPEA/RJ', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00002/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-03-27', 'data_publicacao': '2023-03-31', 'data_proposta_comercial': '2023-03-20', 'vigencia_inicio': '2023-03-27', 'vigencia_fim': '2023-05-24', 'valor_inicial': '21.000,00', 'valor_global': '21.000,00', 'num_parcelas': 1, 'valor_parcela': '21.000,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '30.773.398/0001-60', 'fornecedor_nome': 'KAY COMERCIO E PRESTACAO DE SERVICOS LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/187512/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/187512/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/187512/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/187512/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/187512/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/187512/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/187512/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/187512/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/187512/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/187512/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/187512/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/187512/arquivos'}, {'id': 187522, 'receita_despesa': 'Despesa', 'numero': '2023NE000033', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'SEINF', 'processo': '03001.000001/2023-37', 'objeto': 'SERVIÇO DE INSTALAÇÃO DE 06 SPLITS DE AR CONDICIONADO DE 12.000 BTUS, VISANDO PROPICIAR, DURANTE A NOITE, FINS DE SEMANA E FERIADOS, MELHOR CLIMATIZAÇÃO DAS SALAS DOS RACKS QUE ACONDICIONAM OS SWITCHES DE REDE, LOCALIZADOS NO 17º ANDAR - (SALA 1720), 18º ANDAR - (SALA 1819) E 19º ANDAR - (SALA 1911) DO IPEA/RJ', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00002/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-03-27', 'data_publicacao': '2023-03-31', 'data_proposta_comercial': None, 'vigencia_inicio': '2023-03-27', 'vigencia_fim': '2023-05-24', 'valor_inicial': '8.100,00', 'valor_global': '8.100,00', 'num_parcelas': 1, 'valor_parcela': '8.100,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '30.773.398/0001-60', 'fornecedor_nome': 'KAY COMERCIO E PRESTACAO DE SERVICOS LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/187522/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/187522/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/187522/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/187522/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/187522/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/187522/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/187522/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/187522/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/187522/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/187522/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/187522/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/187522/arquivos'}, {'id': 188999, 'receita_despesa': 'Despesa', 'numero': '2023NE000034', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.001058/2023-53', 'objeto': 'CONTRATAÇÃO DE SERVIÇO DE INTERNET -LINK DE INTERNET REDUNDANTE PARA O IPEA - UNIDADE RIO DE JANEIRO', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: I', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00003/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-04-04', 'data_publicacao': '2023-04-13', 'data_proposta_comercial': '2023-03-30', 'vigencia_inicio': '2023-04-04', 'vigencia_fim': '2024-04-04', 'valor_inicial': '14.149,56', 'valor_global': '14.149,56', 'num_parcelas': 1, 'valor_parcela': '14.149,56', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '72.843.212/0001-41', 'fornecedor_nome': 'CIRION TECHNOLOGIES DO BRASIL LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/188999/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/188999/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/188999/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/188999/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/188999/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/188999/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/188999/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/188999/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/188999/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/188999/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/188999/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/188999/arquivos'}, {'id': 192611, 'receita_despesa': 'Despesa', 'numero': '2023NE000035', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SEINF', 'processo': '03001.000715/2023-45', 'objeto': 'AQUISIÇÃO DE 20 (VINTE) UNIDADES DE ARMAZENAMENTO INTERNO (HARD DRIVES -- SSD)', 'amparo_legal': 'LEI 10.520 / 2002 - Artigo: 1', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '153032', 'licitacao_numero': '00071/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-04-11', 'data_publicacao': '2023-05-09', 'data_proposta_comercial': None, 'vigencia_inicio': '2023-04-11', 'vigencia_fim': '2023-05-24', 'valor_inicial': '3.400,00', 'valor_global': '3.400,00', 'num_parcelas': 1, 'valor_parcela': '3.400,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '34.238.351/0001-57', 'fornecedor_nome': 'P & F IMPORTACAO E EXPORTACAO LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/192611/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/192611/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/192611/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/192611/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/192611/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/192611/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/192611/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/192611/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/192611/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/192611/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/192611/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/192611/arquivos'}, {'id': 194180, 'receita_despesa': 'Despesa', 'numero': '2023NE000040', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.000540/2023-76', 'objeto': 'CONTRATAÇÃO DE EMPRESA ESPECIALIZADA NA EXECUÇÃO DE SERVIÇOS DE ENGENHARIA (NÃO CONTINUADOS) PARA LIMPEZA ROBOTIZADA, POR ESCOVAÇÃO A SECO COM FILMAGEM SIMULTÂNEA, DE REDE DE DUTOS E DESCONTAMINAÇÃO DE SISTEMA DE AR CONDICIONADO E VENTILAÇÃO, COMPREENDENDO OS SEGUINTES SERVIÇOS: LIMPEZA E DESCONTAMIZAÇÃO 01 (UMA) VEZ AO ANO; EMISSÃO DE LAUDOS COM AVALIAÇÃO DA QUALIDADE DO AR 02 (DUAS) VEZES AO ANO, A CADA 06 (SEIS) MESES.', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00006/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-05-17', 'data_publicacao': '2023-05-19', 'data_proposta_comercial': '2023-05-16', 'vigencia_inicio': '2023-05-17', 'vigencia_fim': '2023-12-31', 'valor_inicial': '2.600,00', 'valor_global': '2.600,00', 'num_parcelas': 3, 'valor_parcela': '866,67', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '02.819.827/0001-57', 'fornecedor_nome': 'O. A. M. COMERCIAL E SERVICOS LTDA.', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/194180/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/194180/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/194180/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/194180/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/194180/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/194180/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/194180/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/194180/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/194180/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/194180/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/194180/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/194180/arquivos'}, {'id': 195184, 'receita_despesa': 'Despesa', 'numero': '2023NE000041', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.000543/2023-18', 'objeto': 'AQUISIÇÃO DE 270 KG DECAFÉ EM PÓ, TORRADO E MOÍDO, EMBALADO À VÁCUO (TORRA MÉDIA ) (18 MESES NO MÍNIMO DE VALIDADE) CONFORME CONDIÇÕES E EXIGÊNCIAS ESTABELECIDAS NO TERMO DE REFERÊNCIA', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00005/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-05-18', 'data_publicacao': '2023-05-25', 'data_proposta_comercial': '2023-05-10', 'vigencia_inicio': '2023-05-18', 'vigencia_fim': '2023-06-23', 'valor_inicial': '7.246,80', 'valor_global': '7.246,80', 'num_parcelas': 1, 'valor_parcela': '7.246,80', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '27.245.852/0001-03', 'fornecedor_nome': 'SUL BRASIL ATACADISTA LIMITADA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/195184/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/195184/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/195184/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/195184/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/195184/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/195184/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/195184/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/195184/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/195184/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/195184/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/195184/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/195184/arquivos'}, {'id': 195195, 'receita_despesa': 'Despesa', 'numero': '2023NE000042', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.000543/2023-18', 'objeto': 'AQUISIÇÃO DE 135 KG DE AÇÚCAR REFINADO, BRANCO, EMBALAGEM DE 1KG (12 MESES NO MÍNIMO DE VALIDADE), E DE 108 FRASCOS DE ADOÇANTE LÍQUIDO SUCRALOSE - FRASCO DE 100ML (18 MESES NO MÍNIMO DE VALIDADE) CONFORME CONDIÇÕES E EXIGÊNCIAS ESTABELECIDAS NO TERMO DE REFERÊNCIA', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00005/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-05-18', 'data_publicacao': '2023-05-25', 'data_proposta_comercial': '2023-05-15', 'vigencia_inicio': '2023-05-18', 'vigencia_fim': '2023-06-23', 'valor_inicial': '1.852,20', 'valor_global': '1.852,20', 'num_parcelas': 1, 'valor_parcela': '1.852,20', 'valor_acumulado': '1.852,20', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '39.643.017/0001-00', 'fornecedor_nome': 'NGV DISTRIBUIDORA E SOLUCOES LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/195195/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/195195/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/195195/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/195195/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/195195/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/195195/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/195195/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/195195/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/195195/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/195195/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/195195/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/195195/arquivos'}, {'id': 197072, 'receita_despesa': 'Despesa', 'numero': '2023NE000045', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.000543/2023-18', 'objeto': 'AQUISIÇÃO DE 09 KG DE CAFÉ TORRADO EM GRÃOS, CONFORME CONDIÇÕES E EXIGÊNCIAS ESTABELECIDAS NO TERMO DE REFERÊNCIA –SEI (0531607).', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00007/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-05-31', 'data_publicacao': '2023-06-06', 'data_proposta_comercial': '2023-05-22', 'vigencia_inicio': '2023-05-31', 'vigencia_fim': '2023-06-23', 'valor_inicial': '702,00', 'valor_global': '702,00', 'num_parcelas': 1, 'valor_parcela': '702,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '31.021.788/0001-46', 'fornecedor_nome': 'F PEREIRA COMERCIO E DISTRIBUIDORA LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/197072/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/197072/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/197072/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/197072/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/197072/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/197072/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/197072/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/197072/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/197072/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/197072/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/197072/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/197072/arquivos'}, {'id': 200160, 'receita_despesa': 'Despesa', 'numero': '2023NE000039', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'COACC', 'processo': '03001.001470/2023-73', 'objeto': 'CAPACITAÇÃO DE SERVIDOR NO \"10º CONTRATOS WEEK - SEMANA NACIONAL DE ESTUDOS AVANÇADOS EM CONTRATOS ADMINISTRATIVOS\" REALIZADO EM FOZ DO IGUAÇU - PARANÁ, NO PERÍODO DE 12 A 16/06/2023', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 25', 'informacao_complementar': None, 'codigo_modalidade': '07', 'modalidade': 'Inexigibilidade', 'unidade_compra': '113602', 'licitacao_numero': '00003/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-05-16', 'data_publicacao': '2023-06-28', 'data_proposta_comercial': None, 'vigencia_inicio': '2023-05-16', 'vigencia_fim': '2023-07-04', 'valor_inicial': '5.399,00', 'valor_global': '5.399,00', 'num_parcelas': 1, 'valor_parcela': '5.399,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '10.498.974/0002-81', 'fornecedor_nome': 'INSTITUTO NEGOCIOS PUBLICOS DO BRASIL - ESTUDOS E PESQUISAS NA ADMNIISTRACAO PUB', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/200160/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/200160/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/200160/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/200160/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/200160/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/200160/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/200160/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/200160/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/200160/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/200160/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/200160/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/200160/arquivos'}, {'id': 201254, 'receita_despesa': 'Despesa', 'numero': '2023NE000038', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.000697/2023-00', 'objeto': 'SERVIÇO DE RECARGA NOS EXTINTORES PERTENCNETES AO IPEA/RJ', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00004/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-05-02', 'data_publicacao': '2023-07-05', 'data_proposta_comercial': '2023-04-25', 'vigencia_inicio': '2023-05-02', 'vigencia_fim': '2023-07-10', 'valor_inicial': '1.000,00', 'valor_global': '1.000,00', 'num_parcelas': 1, 'valor_parcela': '1.000,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '22.159.233/0001-74', 'fornecedor_nome': 'T. V. DA SILVA SERVICOS E EQUIPAMENTOS CONTRA INCENDIO', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/201254/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/201254/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/201254/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/201254/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/201254/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/201254/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/201254/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/201254/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/201254/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/201254/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/201254/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/201254/arquivos'}, {'id': 214627, 'receita_despesa': 'Despesa', 'numero': '2023NE000049', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.002537/2023-97', 'objeto': 'AQUISIÇÃO DE 01 UNIDADE DE REFRIERADOR DUPLEX', 'amparo_legal': 'LEI 10.520 / 2002 - Artigo: 1', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '160228', 'licitacao_numero': '00004/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-09-08', 'data_publicacao': '2023-09-22', 'data_proposta_comercial': '2023-01-16', 'vigencia_inicio': '2023-09-08', 'vigencia_fim': '2023-12-31', 'valor_inicial': '2.627,00', 'valor_global': '2.627,00', 'num_parcelas': 1, 'valor_parcela': '2.627,00', 'valor_acumulado': '2.627,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '28.315.329/0001-60', 'fornecedor_nome': 'LL COMERCIO DE EQUIPAMENTOS LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/214627/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/214627/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/214627/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/214627/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/214627/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/214627/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/214627/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/214627/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/214627/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/214627/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/214627/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/214627/arquivos'}, {'id': 214630, 'receita_despesa': 'Despesa', 'numero': '2023NE000052', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'SEEOF', 'processo': '03001.002845/2023-12', 'objeto': 'CAPACITAÇÃO DOS SERVIDORES MARIA HOSANA CARNEIRO DA CUNHA E RAUL JOSÉ CORDEIRO LEMOS - \"CURSO SIAFI – TEÓRICO E PRÁTICO DE EXECUÇÃO ORÇAMENTÁRIA E FINANCEIRA NO SISTEMA\"', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 25', 'informacao_complementar': None, 'codigo_modalidade': '07', 'modalidade': 'Inexigibilidade', 'unidade_compra': '113602', 'licitacao_numero': '00005/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-09-15', 'data_publicacao': '2023-09-22', 'data_proposta_comercial': '2023-09-05', 'vigencia_inicio': '2023-09-15', 'vigencia_fim': '2023-11-22', 'valor_inicial': '3.180,00', 'valor_global': '3.180,00', 'num_parcelas': 1, 'valor_parcela': '3.180,00', 'valor_acumulado': '3.180,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '14.087.594/0001-24', 'fornecedor_nome': 'MMP CURSOS CAPACITACAO E TREINAMENTO LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/214630/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/214630/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/214630/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/214630/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/214630/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/214630/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/214630/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/214630/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/214630/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/214630/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/214630/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/214630/arquivos'}, {'id': 214631, 'receita_despesa': 'Despesa', 'numero': '2023NE000053', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.001989/2023-51', 'objeto': 'AQUISIÇÃO DE 350 PACOTES COM 1000 FOLHAS - PAPEL TOALHA, COMPRIMENTO 20 CM, LARGURA 21 CM, COR BRANCA, INTERFOLHADA, 2 DOBRAS', 'amparo_legal': 'LEI 10.520 / 2002 - Artigo: 1', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '160242', 'licitacao_numero': '00058/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-09-19', 'data_publicacao': '2023-09-22', 'data_proposta_comercial': '2022-11-14', 'vigencia_inicio': '2023-09-19', 'vigencia_fim': '2023-10-31', 'valor_inicial': '3.325,00', 'valor_global': '3.325,00', 'num_parcelas': 1, 'valor_parcela': '3.325,00', 'valor_acumulado': '3.325,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '22.965.625/0001-20', 'fornecedor_nome': 'LABUTAR DISTRIBUIDORA E PRESTADORA DE SERVICO LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/214631/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/214631/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/214631/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/214631/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/214631/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/214631/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/214631/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/214631/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/214631/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/214631/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/214631/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/214631/arquivos'}, {'id': 214632, 'receita_despesa': 'Despesa', 'numero': '2023NE000054', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.002537/2023-97', 'objeto': 'AQUISIÇÃO DE 2 UNIDADES TELEVISOR, SMART TV, 4K, WIFI, ENTRADAS HDMI/USB, TELA 65 POLEGADAS, TELA TIPO LED, VOLTAGEM 110/220V, CONTROLE REMOTO.', 'amparo_legal': 'LEI 10.520 / 2002 - Artigo: 1', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '153254', 'licitacao_numero': '00015/2022', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-09-20', 'data_publicacao': '2023-09-22', 'data_proposta_comercial': '2022-11-25', 'vigencia_inicio': '2023-09-20', 'vigencia_fim': '2023-12-31', 'valor_inicial': '6.900,00', 'valor_global': '6.900,00', 'num_parcelas': 1, 'valor_parcela': '6.900,00', 'valor_acumulado': '6.900,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '65.149.197/0002-51', 'fornecedor_nome': 'REPREMIG REPRESENTACAO E COMERCIO DE MINAS GERAIS LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/214632/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/214632/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/214632/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/214632/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/214632/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/214632/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/214632/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/214632/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/214632/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/214632/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/214632/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/214632/arquivos'}, {'id': 218514, 'receita_despesa': 'Despesa', 'numero': '2023NE000056', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Informática (TIC)', 'subcategoria': None, 'unidades_requisitantes': 'Biblioteca', 'processo': '03001.001516/2023-54', 'objeto': 'AQUISIÇÃO DO SOFTWARE DE OCR, EM LÍNGUA PORTUGUESA, COM 10 LICENÇAS PERPÉTUAS, PARA USO DAS BIBLIOTECAS DO IPEA DO RIO DE JANEIRO E BRASÍLIA, SEUS USUÁRIOS, COMO DE USO PARA PESQUISA ACADÊMICA', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00008/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-09-21', 'data_publicacao': '2023-09-29', 'data_proposta_comercial': '2023-09-08', 'vigencia_inicio': '2023-09-21', 'vigencia_fim': '2023-10-30', 'valor_inicial': '11.010,70', 'valor_global': '11.010,70', 'num_parcelas': 1, 'valor_parcela': '11.010,70', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '12.007.998/0001-35', 'fornecedor_nome': 'PISONTEC COMERCIO E SERVICOS EM TECNOLOGIA DA INFORMACAO LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/218514/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/218514/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/218514/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/218514/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/218514/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/218514/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/218514/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/218514/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/218514/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/218514/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/218514/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/218514/arquivos'}, {'id': 218520, 'receita_despesa': 'Despesa', 'numero': '2023NE000055', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Informática (TIC)', 'subcategoria': None, 'unidades_requisitantes': 'Biblioteca', 'processo': '03001.001531/2023-01', 'objeto': 'AQUISIÇÃO DE 02 (DOIS) SCANNERS PLANETÁRIOS, PARA USO DAS BIBLIOTECAS DO IPEA DO RIO DE JANEIRO E BRASÍLIA, SEUS USUÁRIOS, COMO DE USO PARA PESQUISA ACADÊMICA. A AQUISIÇÃO DESSES SCANNERS TEM A FINALIDADE DE VIABILIZAR A DIGITALIZAÇÃO DO ACERVO DA MEMÓRIA BIBLIOGRÁFICA DO IPEA, QUE ATUALMENTE É PRESERVADO EM FORMATO FÍSICO PELA BIBLIOTECA DO IPEA, EM SUAS UNIDADES EM BRASÍLIA E NO RIO DE JANEIRO.', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 25', 'informacao_complementar': None, 'codigo_modalidade': '07', 'modalidade': 'Inexigibilidade', 'unidade_compra': '113602', 'licitacao_numero': '00006/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-09-21', 'data_publicacao': '2023-09-29', 'data_proposta_comercial': '2023-08-28', 'vigencia_inicio': '2023-09-21', 'vigencia_fim': '2023-11-08', 'valor_inicial': '9.800,00', 'valor_global': '9.800,00', 'num_parcelas': 1, 'valor_parcela': '9.800,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '01.464.579/0001-06', 'fornecedor_nome': 'SCANSYSTEM LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/218520/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/218520/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/218520/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/218520/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/218520/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/218520/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/218520/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/218520/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/218520/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/218520/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/218520/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/218520/arquivos'}, {'id': 225188, 'receita_despesa': 'Despesa', 'numero': '2023NE000063', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.002119/2023-08', 'objeto': 'AQUISIÇÃO DE MATERIAL DE CONSUMO PARA ATENDIMENTO DAS DEMANDAS APRESENTADAS PELO SELOP/ALMOXARIFADO NECESSÁRIAS PARA COMPOR ESTOQUE DE ALMOXARIFADO - ITEM 1 (CARTÃO DE VISITA) E ATENDIMENTO DO SERVIÇO DE COPEIRAGEM - ITEM 4 (GARRAFA TÉRMICA MATERIAL DE AÇO INOXIDÁVEL)', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00009/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-11-03', 'data_publicacao': '2023-11-07', 'data_proposta_comercial': '2023-09-11', 'vigencia_inicio': '2023-11-03', 'vigencia_fim': '2023-11-30', 'valor_inicial': '2.208,00', 'valor_global': '2.208,00', 'num_parcelas': 1, 'valor_parcela': '2.208,00', 'valor_acumulado': '2.208,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '21.666.913/0001-11', 'fornecedor_nome': 'COMERCIAL LB MIX LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/225188/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/225188/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/225188/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/225188/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/225188/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/225188/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/225188/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/225188/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/225188/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/225188/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/225188/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/225188/arquivos'}, {'id': 225193, 'receita_despesa': 'Despesa', 'numero': '2023NE000062', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.002119/2023-08', 'objeto': 'AQUISIÇÃO DE MATERIAL DE CONSUMO PARA ATENDIMENTO DAS DEMANDAS APRESENTADAS PELO SELOP/ALMOXARIFADO NECESSÁRIAS PARA O ATENDIMENTO DO SERVIÇO DE COPEIRAGEM- ITENS 2 (BANDEJA DE AÇO MATERIAL: AÇO INOXIDÁVEL), 3 (LEITEIRA MATERIAL: ALUMÍNIO) E 5 (GARRAFA TÉRMICA MATERIAL: PLÁSTICO RESISTENTE)', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00010/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-11-03', 'data_publicacao': '2023-11-07', 'data_proposta_comercial': '2023-09-05', 'vigencia_inicio': '2023-11-03', 'vigencia_fim': '2023-12-19', 'valor_inicial': '2.093,90', 'valor_global': '2.093,90', 'num_parcelas': 1, 'valor_parcela': '2.093,90', 'valor_acumulado': '2.093,90', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '13.808.799/0001-99', 'fornecedor_nome': 'LPK UTILIDADES LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/225193/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/225193/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/225193/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/225193/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/225193/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/225193/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/225193/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/225193/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/225193/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/225193/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/225193/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/225193/arquivos'}, {'id': 225448, 'receita_despesa': 'Despesa', 'numero': '2023NE000061', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SEINF', 'processo': '03001.002539/2023-86', 'objeto': 'AQUISIÇÃO DE MATERIAL DE CONSUMO TIC- MEMORIAS RAM- \" ITEM 1- MEMÓRIA RAM 8GB – DIMM – DDR3 – 1600MHZ – EQUIVALENTE AO MODELO KINGSTON KVR16N11/8– QUANTIDADE: 22 UNIDADES”.', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00011/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-11-03', 'data_publicacao': '2023-11-07', 'data_proposta_comercial': '2023-09-19', 'vigencia_inicio': '2023-11-03', 'vigencia_fim': '2023-12-12', 'valor_inicial': '1.857,68', 'valor_global': '1.857,68', 'num_parcelas': 1, 'valor_parcela': '1.857,68', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '20.473.312/0001-20', 'fornecedor_nome': 'A C P DA SILVA QUINOY COMERCIO E SERVICOS', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/225448/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/225448/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/225448/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/225448/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/225448/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/225448/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/225448/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/225448/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/225448/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/225448/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/225448/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/225448/arquivos'}, {'id': 225987, 'receita_despesa': 'Despesa', 'numero': '2023NE000064', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Informática (TIC)', 'subcategoria': None, 'unidades_requisitantes': 'SEINF', 'processo': '03001.002539/2023-86', 'objeto': 'AQUISIÇÃO DE 50 UNIDADES MEMÓRIA RAM 16GB - ITEM 2', 'amparo_legal': 'LEI 8.666 / 1993 - Artigo: 24 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00012/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-11-07', 'data_publicacao': '2023-11-10', 'data_proposta_comercial': None, 'vigencia_inicio': '2023-11-07', 'vigencia_fim': '2023-12-12', 'valor_inicial': '11.000,00', 'valor_global': '11.000,00', 'num_parcelas': 1, 'valor_parcela': '11.000,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '48.837.707/0001-83', 'fornecedor_nome': 'MDP COMERCIO E IMPORTACAO LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/225987/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/225987/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/225987/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/225987/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/225987/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/225987/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/225987/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/225987/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/225987/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/225987/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/225987/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/225987/arquivos'}, {'id': 233804, 'receita_despesa': 'Despesa', 'numero': '2023NE000065', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.002119/2023-08', 'objeto': 'AQUISIÇÃO DE MATERIAL PERMANENTE (03 CARRINHOS DE DISTRIBUIÇÃO PARA USO NO SERVIÇO DE COPEIRAGEM)', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '00013/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-11-30', 'data_publicacao': '2023-12-13', 'data_proposta_comercial': '2023-11-21', 'vigencia_inicio': '2023-11-30', 'vigencia_fim': '2023-12-31', 'valor_inicial': '9.670,50', 'valor_global': '9.670,50', 'num_parcelas': 1, 'valor_parcela': '9.670,50', 'valor_acumulado': '9.670,50', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '48.807.338/0001-86', 'fornecedor_nome': '48.807.338 WILLIAM BONILHA DE ARAUJO', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/233804/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/233804/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/233804/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/233804/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/233804/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/233804/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/233804/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/233804/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/233804/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/233804/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/233804/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/233804/arquivos'}, {'id': 233808, 'receita_despesa': 'Despesa', 'numero': '2023NE000066', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': 'SEINF', 'processo': '03001.001989/2023-51', 'objeto': 'AQUISIÇÃO DE MATERIAL PERMANENTE - ESCADA ARTICULADA DE ALUMÍNIO DE 4 METROS COM 20 DEDGRAUS PARA USO DO SETOR DE INFORMÁTICA', 'amparo_legal': 'LEI 10.520 / 2002 - Artigo: 1', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '194075', 'licitacao_numero': '00002/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2023-12-07', 'data_publicacao': '2023-12-13', 'data_proposta_comercial': '2023-05-12', 'vigencia_inicio': '2023-12-07', 'vigencia_fim': '2023-12-22', 'valor_inicial': '950,00', 'valor_global': '950,00', 'num_parcelas': 1, 'valor_parcela': '950,00', 'valor_acumulado': '950,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '38.292.219/0001-84', 'fornecedor_nome': 'RAFAEL RODRIGUES CHOAIRY RODART', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/233808/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/233808/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/233808/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/233808/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/233808/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/233808/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/233808/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/233808/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/233808/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/233808/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/233808/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/233808/arquivos'}, {'id': 251883, 'receita_despesa': 'Despesa', 'numero': '00001/2024', 'codigo_tipo': '50', 'tipo': 'Contrato', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'SEINF E SELOP', 'processo': '03001.002213/2023-59', 'objeto': 'CONTRATAÇÃO DE SOLUÇÃO DE TECNOLOGIA DA INFORMAÇÃO E COMUNICAÇÃO DE SERVIÇO DE OUTSOURCING DE IMPRESSÃO, POR MEIO DE DISPONIBILIZAÇÃO DE MÁQUINAS FUNCIONAIS NOVAS, DE PRIMEIRO USO, PARA PRESTAÇÃO DE SERVIÇOS DE REPROGRAFIA DE DOCUMENTOS, COMPREENDENDO REPRODUÇÃO, IMPRESSÃO, DIGITALIZAÇÃO, INSTALAÇÃO, TREINAMENTO E CONFIGURAÇÃO DE SOFTWARE DE GERENCIAMENTO E BILHETAGEM COM GARANTIA DE FUNCIONAMENTO DA SOLUÇÃO, COM A DEVIDA MANUTENÇÃO E FORNECIMENTO DE SUPRIMENTOS, EXCETO PAPEL, SEM PREVISÃO DE CONSUMO MÍNIMO, PELO PERÍODO DE 48 MESES', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 28 - Inciso: I', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '113602', 'licitacao_numero': '90015/2024', 'sistema_origem_licitacao': None, 'data_assinatura': '2024-02-20', 'data_publicacao': '2024-02-22', 'data_proposta_comercial': '2024-01-18', 'vigencia_inicio': '2024-04-02', 'vigencia_fim': '2028-04-02', 'valor_inicial': '270.862,44', 'valor_global': '270.862,44', 'num_parcelas': 48, 'valor_parcela': '5.642,97', 'valor_acumulado': '270.862,56', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '07.366.296/0001-08', 'fornecedor_nome': 'NEO TECNOLOGIA DA INFORMATICA LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/251883/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/251883/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/251883/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/251883/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/251883/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/251883/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/251883/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/251883/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/251883/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/251883/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/251883/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/251883/arquivos'}, {'id': 252332, 'receita_despesa': 'Despesa', 'numero': '00002/2024', 'codigo_tipo': '50', 'tipo': 'Contrato', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'SEINF', 'processo': '03001.002713/2023-91', 'objeto': 'PRESTAÇÃO DE SERVIÇO DE ACESSO À INTERNET, COM PRAZO INICIAL DE 24 (VINTE E QUATRO) MESES, PODENDO SER PRORROGADO NOS TERMOS DA LEI Nº 14.133 DE 01/04/2021, UTILIZANDO LINKS DE ACESSO COM CAPACIDADE DE PROVER COMUNICAÇÃO DE DADOS, VOZ E IMAGENS POR COMUTAÇÃO DE PACOTES IP (INTERNET PROTOCOL)', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '90027/2024', 'sistema_origem_licitacao': None, 'data_assinatura': '2024-02-21', 'data_publicacao': '2024-02-23', 'data_proposta_comercial': '2024-02-09', 'vigencia_inicio': '2024-04-05', 'vigencia_fim': '2026-04-05', 'valor_inicial': '38.832,00', 'valor_global': '38.832,00', 'num_parcelas': 24, 'valor_parcela': '1.618,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '08.804.362/0001-47', 'fornecedor_nome': 'FACHINELI COMUNICACAO LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/252332/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/252332/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/252332/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/252332/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/252332/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/252332/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/252332/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/252332/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/252332/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/252332/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/252332/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/252332/arquivos'}, {'id': 263191, 'receita_despesa': 'Despesa', 'numero': '2024NE000017', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': '', 'processo': '03001.003819/2023-10', 'objeto': '33903007 - GENEROS DE ALIMENTAÇÃO - CAFÉ.', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': '11360206900142024 - UASG Minuta: 113602', 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '90014/2024', 'sistema_origem_licitacao': None, 'data_assinatura': '2024-03-27', 'data_publicacao': '2024-03-27', 'data_proposta_comercial': None, 'vigencia_inicio': '2024-03-27', 'vigencia_fim': '2024-04-19', 'valor_inicial': '6.641,60', 'valor_global': '6.641,60', 'num_parcelas': 1, 'valor_parcela': '6.641,60', 'valor_acumulado': '6.641,60', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '53.640.283/0001-00', 'fornecedor_nome': 'HIPER COMERCIO & SERVICOS LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/263191/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/263191/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/263191/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/263191/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/263191/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/263191/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/263191/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/263191/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/263191/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/263191/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/263191/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/263191/arquivos'}, {'id': 264096, 'receita_despesa': 'Despesa', 'numero': '2024NE000016', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.003819/2023-10', 'objeto': '98 FRASCOS DE ADOÇANTE', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '90014/2024', 'sistema_origem_licitacao': None, 'data_assinatura': '2024-03-19', 'data_publicacao': '2024-04-25', 'data_proposta_comercial': '2024-03-15', 'vigencia_inicio': '2024-03-19', 'vigencia_fim': '2024-04-19', 'valor_inicial': '988,82', 'valor_global': '988,82', 'num_parcelas': 1, 'valor_parcela': '988,82', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '27.494.420/0001-28', 'fornecedor_nome': 'SOARES COMERCIO E LICITACOES LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/264096/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/264096/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/264096/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/264096/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/264096/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/264096/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/264096/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/264096/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/264096/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/264096/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/264096/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/264096/arquivos'}, {'id': 264097, 'receita_despesa': 'Despesa', 'numero': '2024NE000015', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Compras', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.003819/2023-10', 'objeto': '161 KG DE AÇÚCAR REFINADO, BRANCO', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '90014/2024', 'sistema_origem_licitacao': None, 'data_assinatura': '2024-03-19', 'data_publicacao': '2024-04-25', 'data_proposta_comercial': '2024-03-14', 'vigencia_inicio': '2024-03-19', 'vigencia_fim': '2024-04-19', 'valor_inicial': '818,85', 'valor_global': '818,85', 'num_parcelas': 1, 'valor_parcela': '818,85', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '52.120.603/0001-20', 'fornecedor_nome': '52.120.603 SILVANIA ABECASSIS DE CARVALHO', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/264097/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/264097/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/264097/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/264097/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/264097/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/264097/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/264097/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/264097/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/264097/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/264097/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/264097/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/264097/arquivos'}, {'id': 264441, 'receita_despesa': 'Despesa', 'numero': '2024NE000018', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços de Engenharia', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.000744/2024-98', 'objeto': 'LIMPEZA ROBOTIZADA, POR ESCOVAÇÃO A SECO COM FILMAGEM SIMULTÂNEA, DE REDE DE DUTOS E DESCONTAMINAÇÃO DE SISTEMA DE AR CONDICIONADO E VENTILAÇÃO DA SALA DO CPD DO IPEA/RJ', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: I', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '90023/2024', 'sistema_origem_licitacao': None, 'data_assinatura': '2024-04-19', 'data_publicacao': '2024-04-26', 'data_proposta_comercial': '2024-04-09', 'vigencia_inicio': '2024-04-19', 'vigencia_fim': '2024-12-31', 'valor_inicial': '2.962,50', 'valor_global': '2.962,50', 'num_parcelas': 1, 'valor_parcela': '2.962,50', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '36.724.325/0001-64', 'fornecedor_nome': 'GERIR COMERCIO E SERVICOS DE REFRIGERACAO LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/264441/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/264441/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/264441/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/264441/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/264441/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/264441/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/264441/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/264441/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/264441/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/264441/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/264441/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/264441/arquivos'}, {'id': 295851, 'receita_despesa': 'Despesa', 'numero': '00003/2024', 'codigo_tipo': '50', 'tipo': 'Contrato', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': None, 'processo': '03001.003759/2023-27', 'objeto': 'SERVIÇOS CONTÍNUOS DE COPEIRAGEM, INCLUINDO O FORNECIMENTO DE MATERIAIS DE GÊNERO ALIMENTÍCIO (CAFÉ EM PÓ, AÇÚCAR REFINADO E ADOÇANTE), UNIFORMES E PRODUTOS E INSUMOS NECESSÁRIOS PARA O DESENVOLVIMENTO DAS ATIVIDADES, A SEREM EXECUTADOS EM REGIME DE DEDICAÇÃO EXCLUSIVA DE MÃO DE OBRA, A SEREM EXECUTADOS COM REGIME DE DEDICAÇÃO EXCLUSIVA DE MÃO DE OBRA, NAS CONDIÇÕES ESTABELECIDAS NO TERMO DE REFERÊNCIA', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 28 - Inciso: I', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '113602', 'licitacao_numero': '90026/2024', 'sistema_origem_licitacao': None, 'data_assinatura': '2024-08-02', 'data_publicacao': '2024-08-05', 'data_proposta_comercial': '2024-07-11', 'vigencia_inicio': '2024-09-03', 'vigencia_fim': '2025-09-03', 'valor_inicial': '156.306,60', 'valor_global': '156.306,60', 'num_parcelas': 1, 'valor_parcela': '156.306,60', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '20.591.265/0001-19', 'fornecedor_nome': 'GRUPO OG SERVICOS E VIGILANCIA LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/295851/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/295851/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/295851/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/295851/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/295851/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/295851/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/295851/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/295851/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/295851/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/295851/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/295851/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/295851/arquivos'}, {'id': 299366, 'receita_despesa': 'Despesa', 'numero': '00004/2024', 'codigo_tipo': '50', 'tipo': 'Contrato', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.000307/2024-74', 'objeto': 'CONTRATAÇÃO DE SERVIÇOS COMUNS DE TÁXI CONVENCIONAL, A SEREM REALIZADOS SOB DEMANDA, MEDIANTE “VOUCHER” EM PAPEL E OU/ELETRÔNICO E OU/ APLICATIVO, PARA SUPRIR A NECESSIDADE DE TRANSPORTE TERRESTRE DE SERVIDORES, COLABORADORES, ESTAGIÁRIOS E AUTORIDADES DA UNIDADE DESCENTRALIZADA DO IPEA NO RIO DE JANEIRO/RJ, NO ÂMBITO DO MUNICÍPIO DO RIO DE JANEIRO E SUA REGIÃO METROPOLITANA, EXCLUSIVAMENTE A SERVIÇO, A FIM DE ATENDER ÀS NECESSIDADES DO IPEA/RJ', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '90043/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2024-08-13', 'data_publicacao': '2024-08-14', 'data_proposta_comercial': '2024-07-29', 'vigencia_inicio': '2024-09-17', 'vigencia_fim': '2025-09-17', 'valor_inicial': '9.504,00', 'valor_global': '9.504,00', 'num_parcelas': 12, 'valor_parcela': '792,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '48.442.981/0001-53', 'fornecedor_nome': 'CCRJ AGENCIAMENTO DE TRANSPORTE LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/299366/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/299366/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/299366/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/299366/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/299366/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/299366/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/299366/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/299366/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/299366/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/299366/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/299366/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/299366/arquivos'}, {'id': 305749, 'receita_despesa': 'Despesa', 'numero': '2024NE000021', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'SELOP', 'processo': '03001.000270/2024-84', 'objeto': 'SERVIÇOS DE RECARGA E MANUTENÇÃO DOS EXTINTORES DE COMBATE A INCÊNDIO DO IPEA/RJ', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 75 - Inciso: II', 'informacao_complementar': None, 'codigo_modalidade': '06', 'modalidade': 'Dispensa', 'unidade_compra': '113602', 'licitacao_numero': '90031/2024', 'sistema_origem_licitacao': None, 'data_assinatura': '2024-05-20', 'data_publicacao': '2024-08-30', 'data_proposta_comercial': '2024-04-15', 'vigencia_inicio': '2024-05-21', 'vigencia_fim': '2024-12-31', 'valor_inicial': '721,19', 'valor_global': '721,19', 'num_parcelas': 1, 'valor_parcela': '721,19', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '22.159.233/0001-74', 'fornecedor_nome': 'T. V. DA SILVA SERVICOS E EQUIPAMENTOS CONTRA INCENDIO', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/305749/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/305749/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/305749/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/305749/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/305749/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/305749/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/305749/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/305749/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/305749/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/305749/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/305749/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/305749/arquivos'}, {'id': 306448, 'receita_despesa': 'Despesa', 'numero': '2024NE000014', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'Biblioteca', 'processo': '03001.003685/2023-29', 'objeto': 'ASSINATURA DA VERSÃO DIGITAL DA REVISTA THE ECONOMIST PARA O EXERCÍCIO DE 2024', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 74 - Inciso: I', 'informacao_complementar': None, 'codigo_modalidade': '07', 'modalidade': 'Inexigibilidade', 'unidade_compra': '113602', 'licitacao_numero': '90030/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2024-02-20', 'data_publicacao': '2024-09-03', 'data_proposta_comercial': '2024-02-19', 'vigencia_inicio': '2024-02-21', 'vigencia_fim': '2024-12-31', 'valor_inicial': '1.122,43', 'valor_global': '1.122,43', 'num_parcelas': 1, 'valor_parcela': '1.122,43', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '07.492.558/0001-80', 'fornecedor_nome': 'MEDIA INTERNATIONAL DISTRIBUICAO DE PUBLICACOES LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/306448/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/306448/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/306448/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/306448/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/306448/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/306448/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/306448/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/306448/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/306448/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/306448/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/306448/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/306448/arquivos'}, {'id': 314774, 'receita_despesa': 'Despesa', 'numero': '00005/2024', 'codigo_tipo': '50', 'tipo': 'Contrato', 'subtipo': None, 'prorrogavel': 'Sim', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'SEINF', 'processo': '03001.002678/2023-18', 'objeto': 'CONTRATAÇÃO DE SERVIÇOS COMUNS DE MANUTENÇÃO PREVENTIVA E CORRETIVA DOS EQUIPAMENTOS DE AR-CONDICIONADO DO IPEA/RJ, SEM MÃO DE OBRA EXCLUSIVA, COM O SERVIÇO DE MANUTENÇÃO CORRETIVA E O FORNECIMENTO DE PARTES E PEÇAS EXECUTADOS SOB DEMANDA', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 28 - Inciso: I', 'informacao_complementar': None, 'codigo_modalidade': '05', 'modalidade': 'Pregão', 'unidade_compra': '113602', 'licitacao_numero': '90050/2024', 'sistema_origem_licitacao': None, 'data_assinatura': '2024-09-20', 'data_publicacao': '2024-09-23', 'data_proposta_comercial': '2024-08-06', 'vigencia_inicio': '2024-10-02', 'vigencia_fim': '2029-10-02', 'valor_inicial': '143.350,00', 'valor_global': '143.350,00', 'num_parcelas': 60, 'valor_parcela': '2.389,17', 'valor_acumulado': '143.350,20', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '20.512.135/0001-43', 'fornecedor_nome': 'ECO-ICE SERVICOS DE REFRIGERACAO LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/314774/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/314774/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/314774/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/314774/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/314774/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/314774/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/314774/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/314774/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/314774/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/314774/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/314774/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/314774/arquivos'}, {'id': 321529, 'receita_despesa': 'Despesa', 'numero': '2024NE000029', 'codigo_tipo': '99', 'tipo': 'Empenho', 'subtipo': None, 'prorrogavel': 'Não', 'situacao': 'Ativo', 'justificativa_inativo': None, 'categoria': 'Serviços', 'subcategoria': None, 'unidades_requisitantes': 'COACC E COCCT', 'processo': '03001.002930/2024-61', 'objeto': 'PARTICIPAÇÃO DOS SERVIDORES ISABEL VIRGINIA DE ALENCAR PIRES E MANOEL DE RIBAMAR CARDOSO BARROSO NO CURSO DE \"INTELIGÊNCIA ARTIFICIAL APLICADA ÀS CONTRATAÇÕES PÚBLICAS\", PROMOVIDO PELA CONSULTRE CONSULTORIA E TREINAMENTO LTDA-EPP,CNPJ: 36.003.671/0001-53, A SER REALIZADO NOS DIAS 06/11 A 08/11/2024, DE FORMA PRESENCIAL, NO LOCAL DE REALIZAÇÃO: ROYAL JARDINS BOUTIQUE HOTEL - ALAMEDA JAÚ Nº 729 - JARDIM PAULISTA - SÃO PAULO-SP', 'amparo_legal': 'LEI 14.133/2021 - Artigo: 74 - Inciso: III - Alinea: F', 'informacao_complementar': None, 'codigo_modalidade': '07', 'modalidade': 'Inexigibilidade', 'unidade_compra': '113602', 'licitacao_numero': '90032/2023', 'sistema_origem_licitacao': None, 'data_assinatura': '2024-10-03', 'data_publicacao': '2024-10-09', 'data_proposta_comercial': '2024-08-14', 'vigencia_inicio': '2024-10-03', 'vigencia_fim': '2024-12-04', 'valor_inicial': '8.380,00', 'valor_global': '8.380,00', 'num_parcelas': 1, 'valor_parcela': '8.380,00', 'valor_acumulado': '0,00', 'contratante_orgao_origem_codigo': '61201', 'contratante_orgao_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_codigo': '113602', 'contratante_orgao_origem_unidade_gestora_origem_nome_resumido': 'IPEA/RJ', 'contratante_orgao_origem_unidade_gestora_origem_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_origem_unidade_gestora_origem_sisg': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi': 'Sim', 'contratante_orgao_origem_unidade_gestora_origem_utiliza_antecipagov': 'Sim', 'contratante_orgao_codigo': '61201', 'contratante_orgao_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_codigo': '113602', 'contratante_orgao_unidade_gestora_nome_resumido': 'IPEA/RJ', 'contratante_orgao_unidade_gestora_nome': 'INSTITUTO DE PESQUISA ECONOMICA APLICADA', 'contratante_orgao_unidade_gestora_sisg': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_siafi': 'Sim', 'contratante_orgao_unidade_gestora_utiliza_antecipagov': 'Sim', 'fornecedor_tipo': 'JURIDICA', 'fornecedor_cnpj_cpf_idgener': '36.003.671/0001-53', 'fornecedor_nome': 'CONSULTRE CONSULTORIA E TREINAMENTO LTDA', 'links_historico': 'https://contratos.comprasnet.gov.br/api/contrato/321529/historico', 'links_empenhos': 'https://contratos.comprasnet.gov.br/api/contrato/321529/empenhos', 'links_cronograma': 'https://contratos.comprasnet.gov.br/api/contrato/321529/cronograma', 'links_garantias': 'https://contratos.comprasnet.gov.br/api/contrato/321529/garantias', 'links_itens': 'https://contratos.comprasnet.gov.br/api/contrato/321529/itens', 'links_prepostos': 'https://contratos.comprasnet.gov.br/api/contrato/321529/prepostos', 'links_responsaveis': 'https://contratos.comprasnet.gov.br/api/contrato/321529/responsaveis', 'links_despesas_acessorias': 'https://contratos.comprasnet.gov.br/api/contrato/321529/despesas_acessorias', 'links_faturas': 'https://contratos.comprasnet.gov.br/api/contrato/321529/faturas', 'links_ocorrencias': 'https://contratos.comprasnet.gov.br/api/contrato/321529/ocorrencias', 'links_terceirizados': 'https://contratos.comprasnet.gov.br/api/contrato/321529/terceirizados', 'links_arquivos': 'https://contratos.comprasnet.gov.br/api/contrato/321529/arquivos'}]\n" - ] - } - ], - "source": [ - "from pandas import json_normalize\n", - "flattened = json_normalize(response, sep='_').to_dict(orient='records')\n", - "print(flattened)" - ] - }, - { - "cell_type": "code", - "execution_count": 62, - "id": "b20be2db3e6e3d8", - "metadata": { - "ExecuteTime": { - "end_time": "2025-01-09T20:17:00.536766Z", - "start_time": "2025-01-09T20:17:00.533058Z" - } - }, - "outputs": [], - "source": [ - "for record in flattened:\n", - " for key, value in record.items():\n", - " if type(value) is list:\n", - " print(f\"{key}: {value}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "id": "8c7f614db1672dd3", - "metadata": { - "ExecuteTime": { - "end_time": "2025-01-09T20:07:26.908497Z", - "start_time": "2025-01-09T20:07:24.646656Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[\u001B[34m2025-01-09T21:07:25.922+0100\u001B[0m] {\u001B[34mcliente_base.py:\u001B[0m44} WARNING\u001B[0m - API falhou com status Unknown\u001B[0m\n", - "[\u001B[34m2025-01-09T21:07:26.902+0100\u001B[0m] {\u001B[34m_client.py:\u001B[0m1025} INFO\u001B[0m - HTTP Request: GET https://contratos.comprasnet.gov.br/api/contrato/4153/cronograma \"HTTP/1.1 200 OK\"\u001B[0m\n" - ] - } - ], - "source": [ - "client = ClienteContratos()\n", - "response = client.get_cronograma_by_contrato_id(contrato_id = '4153')" - ] - }, - { - "cell_type": "code", - "execution_count": 46, - "id": "a807e23fd56534a2", - "metadata": { - "ExecuteTime": { - "end_time": "2025-01-09T20:07:32.312139Z", - "start_time": "2025-01-09T20:07:32.302637Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'id': 1026018,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Acordo de Cooperação Técnica (ACT)',\n", - " 'numero': '00014/2019',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CELEBRAÇÃO DO CONTRATO: 0014/2019 DE ACORDO COM PROCESSO NÚMERO: 03001.002870/2019-10',\n", - " 'mesref': '02',\n", - " 'anoref': '2020',\n", - " 'vencimento': '2020-03-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182042,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '07',\n", - " 'anoref': '2020',\n", - " 'vencimento': '2020-08-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182043,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '08',\n", - " 'anoref': '2020',\n", - " 'vencimento': '2020-09-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182044,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '09',\n", - " 'anoref': '2020',\n", - " 'vencimento': '2020-10-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182045,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '10',\n", - " 'anoref': '2020',\n", - " 'vencimento': '2020-11-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182046,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '11',\n", - " 'anoref': '2020',\n", - " 'vencimento': '2020-12-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182047,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '12',\n", - " 'anoref': '2020',\n", - " 'vencimento': '2021-01-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182048,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '01',\n", - " 'anoref': '2021',\n", - " 'vencimento': '2021-02-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182049,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '02',\n", - " 'anoref': '2021',\n", - " 'vencimento': '2021-03-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182050,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '03',\n", - " 'anoref': '2021',\n", - " 'vencimento': '2021-04-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182051,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '04',\n", - " 'anoref': '2021',\n", - " 'vencimento': '2021-05-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182052,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '05',\n", - " 'anoref': '2021',\n", - " 'vencimento': '2021-06-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182053,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '06',\n", - " 'anoref': '2021',\n", - " 'vencimento': '2021-07-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182054,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '07',\n", - " 'anoref': '2021',\n", - " 'vencimento': '2021-08-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182055,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '08',\n", - " 'anoref': '2021',\n", - " 'vencimento': '2021-09-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182056,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '09',\n", - " 'anoref': '2021',\n", - " 'vencimento': '2021-10-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182057,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '10',\n", - " 'anoref': '2021',\n", - " 'vencimento': '2021-11-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182058,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '11',\n", - " 'anoref': '2021',\n", - " 'vencimento': '2021-12-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182059,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '12',\n", - " 'anoref': '2021',\n", - " 'vencimento': '2022-01-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182060,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '01',\n", - " 'anoref': '2022',\n", - " 'vencimento': '2022-02-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182061,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '02',\n", - " 'anoref': '2022',\n", - " 'vencimento': '2022-03-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182062,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '03',\n", - " 'anoref': '2022',\n", - " 'vencimento': '2022-04-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182063,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '04',\n", - " 'anoref': '2022',\n", - " 'vencimento': '2022-05-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182064,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '05',\n", - " 'anoref': '2022',\n", - " 'vencimento': '2022-06-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182065,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '06',\n", - " 'anoref': '2022',\n", - " 'vencimento': '2022-07-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182066,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '07',\n", - " 'anoref': '2022',\n", - " 'vencimento': '2022-08-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182067,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '08',\n", - " 'anoref': '2022',\n", - " 'vencimento': '2022-09-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182068,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '09',\n", - " 'anoref': '2022',\n", - " 'vencimento': '2022-10-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182069,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '10',\n", - " 'anoref': '2022',\n", - " 'vencimento': '2022-11-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182070,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '11',\n", - " 'anoref': '2022',\n", - " 'vencimento': '2022-12-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182071,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '12',\n", - " 'anoref': '2022',\n", - " 'vencimento': '2023-01-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182072,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '01',\n", - " 'anoref': '2023',\n", - " 'vencimento': '2023-02-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182073,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '02',\n", - " 'anoref': '2023',\n", - " 'vencimento': '2023-03-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182074,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '03',\n", - " 'anoref': '2023',\n", - " 'vencimento': '2023-04-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182075,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '04',\n", - " 'anoref': '2023',\n", - " 'vencimento': '2023-05-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182076,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '05',\n", - " 'anoref': '2023',\n", - " 'vencimento': '2023-06-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182077,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '06',\n", - " 'anoref': '2023',\n", - " 'vencimento': '2023-07-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182078,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '07',\n", - " 'anoref': '2023',\n", - " 'vencimento': '2023-08-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182079,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '08',\n", - " 'anoref': '2023',\n", - " 'vencimento': '2023-09-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182080,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '09',\n", - " 'anoref': '2023',\n", - " 'vencimento': '2023-10-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182081,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '10',\n", - " 'anoref': '2023',\n", - " 'vencimento': '2023-11-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182082,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '11',\n", - " 'anoref': '2023',\n", - " 'vencimento': '2023-12-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182083,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '12',\n", - " 'anoref': '2023',\n", - " 'vencimento': '2024-01-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182084,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '01',\n", - " 'anoref': '2024',\n", - " 'vencimento': '2024-02-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182085,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '02',\n", - " 'anoref': '2024',\n", - " 'vencimento': '2024-03-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182086,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '03',\n", - " 'anoref': '2024',\n", - " 'vencimento': '2024-04-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182087,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '04',\n", - " 'anoref': '2024',\n", - " 'vencimento': '2024-05-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182088,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '05',\n", - " 'anoref': '2024',\n", - " 'vencimento': '2024-06-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182089,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '06',\n", - " 'anoref': '2024',\n", - " 'vencimento': '2024-07-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182090,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '07',\n", - " 'anoref': '2024',\n", - " 'vencimento': '2024-08-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182091,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '08',\n", - " 'anoref': '2024',\n", - " 'vencimento': '2024-09-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182092,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '09',\n", - " 'anoref': '2024',\n", - " 'vencimento': '2024-10-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182093,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '10',\n", - " 'anoref': '2024',\n", - " 'vencimento': '2024-11-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182094,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '11',\n", - " 'anoref': '2024',\n", - " 'vencimento': '2024-12-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182095,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '12',\n", - " 'anoref': '2024',\n", - " 'vencimento': '2025-01-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182096,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '01',\n", - " 'anoref': '2025',\n", - " 'vencimento': '2025-02-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'},\n", - " {'id': 1182097,\n", - " 'contrato_id': 4153,\n", - " 'tipo': 'Termo de Apostilamento',\n", - " 'numero': '00001/2020',\n", - " 'receita_despesa': 'Despesa',\n", - " 'observacao': 'CONSTITUI OBJETO DO PRESENTE TERMO DE APOSTILAMENTO A ALTERAÇÃO DOS ENDEREÇOS DA CONTRATANTE, CONSTANTE NO TERMO DE COOPERAÇÃO TÉCNICA Nº 14/2019.\\r\\nSEI 0315466.',\n", - " 'mesref': '02',\n", - " 'anoref': '2025',\n", - " 'vencimento': '2025-03-01',\n", - " 'retroativo': 'Não',\n", - " 'valor': '0,01'}]" - ] - }, - "execution_count": 46, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "response" - ] - }, - { - "metadata": { - "ExecuteTime": { - "end_time": "2025-01-31T19:54:03.665676Z", - "start_time": "2025-01-31T19:54:01.401840Z" - } - }, - "cell_type": "code", - "source": [ - "from requests.auth import HTTPBasicAuth\n", - "from zeep import Transport, Client\n", - "from requests import Session\n", - "\n", - "BEARER_ENDPOINT = \"https://apigateway.conectagov.estaleiro.serpro.gov.br/oauth2/jwt-token/\"\n", - "SOAP_ENDPOINT = \"https://apigateway.conectagov.estaleiro.serpro.gov.br/api-consulta-siape/v1/consulta-siape\"\n", - "\n", - "session = Session()\n", - "session.auth = HTTPBasicAuth('9b47d48f-26fe-49d8-8c74-0a3d91bb06b0', '9828538e-7181-4a84-9f14-d0d3a08b8125')\n", - "transport = Transport(session=session)\n", - "bearer_client = Client(BEARER_ENDPOINT, transport=transport)" - ], - "id": "755fe8e7ee9bb871", - "outputs": [ - { - "ename": "HTTPError", - "evalue": "403 Client Error: Forbidden for url: https://apigateway.conectagov.estaleiro.serpro.gov.br/oauth2/jwt-token/", - "output_type": "error", - "traceback": [ - "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", - "\u001B[0;31mHTTPError\u001B[0m Traceback (most recent call last)", - "Cell \u001B[0;32mIn[1], line 11\u001B[0m\n\u001B[1;32m 9\u001B[0m session\u001B[38;5;241m.\u001B[39mauth \u001B[38;5;241m=\u001B[39m HTTPBasicAuth(\u001B[38;5;124m'\u001B[39m\u001B[38;5;124m9b47d48f-26fe-49d8-8c74-0a3d91bb06b0\u001B[39m\u001B[38;5;124m'\u001B[39m, \u001B[38;5;124m'\u001B[39m\u001B[38;5;124m9828538e-7181-4a84-9f14-d0d3a08b8125\u001B[39m\u001B[38;5;124m'\u001B[39m)\n\u001B[1;32m 10\u001B[0m transport \u001B[38;5;241m=\u001B[39m Transport(session\u001B[38;5;241m=\u001B[39msession)\n\u001B[0;32m---> 11\u001B[0m bearer_client \u001B[38;5;241m=\u001B[39m \u001B[43mClient\u001B[49m\u001B[43m(\u001B[49m\u001B[43mBEARER_ENDPOINT\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mtransport\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mtransport\u001B[49m\u001B[43m)\u001B[49m\n", - "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/site-packages/zeep/client.py:76\u001B[0m, in \u001B[0;36mClient.__init__\u001B[0;34m(self, wsdl, wsse, transport, service_name, port_name, plugins, settings)\u001B[0m\n\u001B[1;32m 74\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mwsdl \u001B[38;5;241m=\u001B[39m wsdl\n\u001B[1;32m 75\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[0;32m---> 76\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mwsdl \u001B[38;5;241m=\u001B[39m \u001B[43mDocument\u001B[49m\u001B[43m(\u001B[49m\u001B[43mwsdl\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mtransport\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43msettings\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43msettings\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 77\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mwsse \u001B[38;5;241m=\u001B[39m wsse\n\u001B[1;32m 78\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mplugins \u001B[38;5;241m=\u001B[39m plugins \u001B[38;5;28;01mif\u001B[39;00m plugins \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m \u001B[38;5;28;01melse\u001B[39;00m []\n", - "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/site-packages/zeep/wsdl/wsdl.py:86\u001B[0m, in \u001B[0;36mDocument.__init__\u001B[0;34m(self, location, transport, base, settings)\u001B[0m\n\u001B[1;32m 77\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_definitions \u001B[38;5;241m=\u001B[39m (\n\u001B[1;32m 78\u001B[0m {}\n\u001B[1;32m 79\u001B[0m ) \u001B[38;5;66;03m# type: typing.Dict[typing.Tuple[str, str], \"Definition\"]\u001B[39;00m\n\u001B[1;32m 80\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mtypes \u001B[38;5;241m=\u001B[39m Schema(\n\u001B[1;32m 81\u001B[0m node\u001B[38;5;241m=\u001B[39m\u001B[38;5;28;01mNone\u001B[39;00m,\n\u001B[1;32m 82\u001B[0m transport\u001B[38;5;241m=\u001B[39m\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mtransport,\n\u001B[1;32m 83\u001B[0m location\u001B[38;5;241m=\u001B[39m\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mlocation,\n\u001B[1;32m 84\u001B[0m settings\u001B[38;5;241m=\u001B[39m\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39msettings,\n\u001B[1;32m 85\u001B[0m )\n\u001B[0;32m---> 86\u001B[0m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mload\u001B[49m\u001B[43m(\u001B[49m\u001B[43mlocation\u001B[49m\u001B[43m)\u001B[49m\n", - "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/site-packages/zeep/wsdl/wsdl.py:89\u001B[0m, in \u001B[0;36mDocument.load\u001B[0;34m(self, location)\u001B[0m\n\u001B[1;32m 88\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;21mload\u001B[39m(\u001B[38;5;28mself\u001B[39m, location):\n\u001B[0;32m---> 89\u001B[0m document \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_get_xml_document\u001B[49m\u001B[43m(\u001B[49m\u001B[43mlocation\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 91\u001B[0m root_definitions \u001B[38;5;241m=\u001B[39m Definition(\u001B[38;5;28mself\u001B[39m, document, \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mlocation)\n\u001B[1;32m 92\u001B[0m root_definitions\u001B[38;5;241m.\u001B[39mresolve_imports()\n", - "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/site-packages/zeep/wsdl/wsdl.py:149\u001B[0m, in \u001B[0;36mDocument._get_xml_document\u001B[0;34m(self, location)\u001B[0m\n\u001B[1;32m 141\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;21m_get_xml_document\u001B[39m(\u001B[38;5;28mself\u001B[39m, location: typing\u001B[38;5;241m.\u001B[39mIO) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m etree\u001B[38;5;241m.\u001B[39m_Element:\n\u001B[1;32m 142\u001B[0m \u001B[38;5;250m \u001B[39m\u001B[38;5;124;03m\"\"\"Load the XML content from the given location and return an\u001B[39;00m\n\u001B[1;32m 143\u001B[0m \u001B[38;5;124;03m lxml.Element object.\u001B[39;00m\n\u001B[1;32m 144\u001B[0m \n\u001B[0;32m (...)\u001B[0m\n\u001B[1;32m 147\u001B[0m \n\u001B[1;32m 148\u001B[0m \u001B[38;5;124;03m \"\"\"\u001B[39;00m\n\u001B[0;32m--> 149\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mload_external\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m 150\u001B[0m \u001B[43m \u001B[49m\u001B[43mlocation\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mtransport\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mlocation\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43msettings\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43msettings\u001B[49m\n\u001B[1;32m 151\u001B[0m \u001B[43m \u001B[49m\u001B[43m)\u001B[49m\n", - "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/site-packages/zeep/loader.py:89\u001B[0m, in \u001B[0;36mload_external\u001B[0;34m(url, transport, base_url, settings)\u001B[0m\n\u001B[1;32m 87\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m base_url:\n\u001B[1;32m 88\u001B[0m url \u001B[38;5;241m=\u001B[39m absolute_location(url, base_url)\n\u001B[0;32m---> 89\u001B[0m content \u001B[38;5;241m=\u001B[39m \u001B[43mtransport\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mload\u001B[49m\u001B[43m(\u001B[49m\u001B[43murl\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 90\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m parse_xml(content, transport, base_url, settings\u001B[38;5;241m=\u001B[39msettings)\n", - "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/site-packages/zeep/transports.py:122\u001B[0m, in \u001B[0;36mTransport.load\u001B[0;34m(self, url)\u001B[0m\n\u001B[1;32m 119\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m response:\n\u001B[1;32m 120\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mbytes\u001B[39m(response)\n\u001B[0;32m--> 122\u001B[0m content \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_load_remote_data\u001B[49m\u001B[43m(\u001B[49m\u001B[43murl\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 124\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mcache:\n\u001B[1;32m 125\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mcache\u001B[38;5;241m.\u001B[39madd(url, content)\n", - "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/site-packages/zeep/transports.py:136\u001B[0m, in \u001B[0;36mTransport._load_remote_data\u001B[0;34m(self, url)\u001B[0m\n\u001B[1;32m 134\u001B[0m response \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39msession\u001B[38;5;241m.\u001B[39mget(url, timeout\u001B[38;5;241m=\u001B[39m\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mload_timeout)\n\u001B[1;32m 135\u001B[0m \u001B[38;5;28;01mwith\u001B[39;00m closing(response):\n\u001B[0;32m--> 136\u001B[0m \u001B[43mresponse\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mraise_for_status\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 137\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m response\u001B[38;5;241m.\u001B[39mcontent\n", - "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/site-packages/requests/models.py:1024\u001B[0m, in \u001B[0;36mResponse.raise_for_status\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 1019\u001B[0m http_error_msg \u001B[38;5;241m=\u001B[39m (\n\u001B[1;32m 1020\u001B[0m \u001B[38;5;124mf\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;132;01m{\u001B[39;00m\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mstatus_code\u001B[38;5;132;01m}\u001B[39;00m\u001B[38;5;124m Server Error: \u001B[39m\u001B[38;5;132;01m{\u001B[39;00mreason\u001B[38;5;132;01m}\u001B[39;00m\u001B[38;5;124m for url: \u001B[39m\u001B[38;5;132;01m{\u001B[39;00m\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39murl\u001B[38;5;132;01m}\u001B[39;00m\u001B[38;5;124m\"\u001B[39m\n\u001B[1;32m 1021\u001B[0m )\n\u001B[1;32m 1023\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m http_error_msg:\n\u001B[0;32m-> 1024\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m HTTPError(http_error_msg, response\u001B[38;5;241m=\u001B[39m\u001B[38;5;28mself\u001B[39m)\n", - "\u001B[0;31mHTTPError\u001B[0m: 403 Client Error: Forbidden for url: https://apigateway.conectagov.estaleiro.serpro.gov.br/oauth2/jwt-token/" - ] - } - ], - "execution_count": 1 - }, - { - "metadata": { - "ExecuteTime": { - "end_time": "2025-01-31T19:54:54.917861Z", - "start_time": "2025-01-31T19:54:53.265544Z" - } - }, - "cell_type": "code", - "source": [ - "# SIAPE\n", - "\n", - "from zeep import Transport, Client\n", - "from requests import Session\n", - "import requests\n", - "\n", - "# Auth endpoint for token\n", - "BEARER_ENDPOINT = \"https://apigateway.conectagov.estaleiro.serpro.gov.br/oauth2/jwt-token/\"\n", - "SOAP_ENDPOINT = \"https://apigateway.conectagov.estaleiro.serpro.gov.br/api-consulta-siape/v1/consulta-siape\"\n", - "\n", - "# Get token\n", - "auth_response = requests.get(\n", - " BEARER_ENDPOINT,\n", - " auth=('9b47d48f-26fe-49d8-8c74-0a3d91bb06b0', '9828538e-7181-4a84-9f14-d0d3a08b8125'),\n", - " headers={'Content-Type': 'application/x-www-form-urlencoded'}\n", - ")\n", - "print(auth_response.text)\n", - "token = auth_response.json()['access_token'] # Adjust based on response format\n", - "\n", - "# Setup SOAP client with token\n", - "session = Session()\n", - "session.headers.update({'Authorization': f'Bearer {token}'})\n", - "transport = Transport(session=session)\n", - "\n", - "# Create SOAP client\n", - "client = Client(SOAP_ENDPOINT, transport=transport)\n", - "\n", - "# Make service calls\n", - "result = client.service.ConsultaDadosAfastamento()" - ], - "id": "e0e543b67f021fb7", - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "

403 Forbidden

\n", - "Request forbidden by administrative rules.\n", - "\n", - "\n" - ] - }, - { - "ename": "JSONDecodeError", - "evalue": "Expecting value: line 1 column 1 (char 0)", - "output_type": "error", - "traceback": [ - "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", - "\u001B[0;31mJSONDecodeError\u001B[0m Traceback (most recent call last)", - "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/site-packages/requests/models.py:974\u001B[0m, in \u001B[0;36mResponse.json\u001B[0;34m(self, **kwargs)\u001B[0m\n\u001B[1;32m 973\u001B[0m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[0;32m--> 974\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mcomplexjson\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mloads\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mtext\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 975\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m JSONDecodeError \u001B[38;5;28;01mas\u001B[39;00m e:\n\u001B[1;32m 976\u001B[0m \u001B[38;5;66;03m# Catch JSON-related errors and raise as requests.JSONDecodeError\u001B[39;00m\n\u001B[1;32m 977\u001B[0m \u001B[38;5;66;03m# This aliases json.JSONDecodeError and simplejson.JSONDecodeError\u001B[39;00m\n", - "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/json/__init__.py:346\u001B[0m, in \u001B[0;36mloads\u001B[0;34m(s, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)\u001B[0m\n\u001B[1;32m 343\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m (\u001B[38;5;28mcls\u001B[39m \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m \u001B[38;5;129;01mand\u001B[39;00m object_hook \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m \u001B[38;5;129;01mand\u001B[39;00m\n\u001B[1;32m 344\u001B[0m parse_int \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m \u001B[38;5;129;01mand\u001B[39;00m parse_float \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m \u001B[38;5;129;01mand\u001B[39;00m\n\u001B[1;32m 345\u001B[0m parse_constant \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m \u001B[38;5;129;01mand\u001B[39;00m object_pairs_hook \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m \u001B[38;5;129;01mand\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m kw):\n\u001B[0;32m--> 346\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43m_default_decoder\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mdecode\u001B[49m\u001B[43m(\u001B[49m\u001B[43ms\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 347\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mcls\u001B[39m \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m:\n", - "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/json/decoder.py:337\u001B[0m, in \u001B[0;36mJSONDecoder.decode\u001B[0;34m(self, s, _w)\u001B[0m\n\u001B[1;32m 333\u001B[0m \u001B[38;5;250m\u001B[39m\u001B[38;5;124;03m\"\"\"Return the Python representation of ``s`` (a ``str`` instance\u001B[39;00m\n\u001B[1;32m 334\u001B[0m \u001B[38;5;124;03mcontaining a JSON document).\u001B[39;00m\n\u001B[1;32m 335\u001B[0m \n\u001B[1;32m 336\u001B[0m \u001B[38;5;124;03m\"\"\"\u001B[39;00m\n\u001B[0;32m--> 337\u001B[0m obj, end \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mraw_decode\u001B[49m\u001B[43m(\u001B[49m\u001B[43ms\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43midx\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m_w\u001B[49m\u001B[43m(\u001B[49m\u001B[43ms\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m0\u001B[39;49m\u001B[43m)\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mend\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 338\u001B[0m end \u001B[38;5;241m=\u001B[39m _w(s, end)\u001B[38;5;241m.\u001B[39mend()\n", - "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/json/decoder.py:355\u001B[0m, in \u001B[0;36mJSONDecoder.raw_decode\u001B[0;34m(self, s, idx)\u001B[0m\n\u001B[1;32m 354\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m \u001B[38;5;167;01mStopIteration\u001B[39;00m \u001B[38;5;28;01mas\u001B[39;00m err:\n\u001B[0;32m--> 355\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m JSONDecodeError(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mExpecting value\u001B[39m\u001B[38;5;124m\"\u001B[39m, s, err\u001B[38;5;241m.\u001B[39mvalue) \u001B[38;5;28;01mfrom\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;28;01mNone\u001B[39;00m\n\u001B[1;32m 356\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m obj, end\n", - "\u001B[0;31mJSONDecodeError\u001B[0m: Expecting value: line 1 column 1 (char 0)", - "\nDuring handling of the above exception, another exception occurred:\n", - "\u001B[0;31mJSONDecodeError\u001B[0m Traceback (most recent call last)", - "Cell \u001B[0;32mIn[3], line 18\u001B[0m\n\u001B[1;32m 12\u001B[0m auth_response \u001B[38;5;241m=\u001B[39m requests\u001B[38;5;241m.\u001B[39mget(\n\u001B[1;32m 13\u001B[0m BEARER_ENDPOINT,\n\u001B[1;32m 14\u001B[0m auth\u001B[38;5;241m=\u001B[39m(\u001B[38;5;124m'\u001B[39m\u001B[38;5;124m9b47d48f-26fe-49d8-8c74-0a3d91bb06b0\u001B[39m\u001B[38;5;124m'\u001B[39m, \u001B[38;5;124m'\u001B[39m\u001B[38;5;124m9828538e-7181-4a84-9f14-d0d3a08b8125\u001B[39m\u001B[38;5;124m'\u001B[39m),\n\u001B[1;32m 15\u001B[0m headers\u001B[38;5;241m=\u001B[39m{\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mContent-Type\u001B[39m\u001B[38;5;124m'\u001B[39m: \u001B[38;5;124m'\u001B[39m\u001B[38;5;124mapplication/x-www-form-urlencoded\u001B[39m\u001B[38;5;124m'\u001B[39m}\n\u001B[1;32m 16\u001B[0m )\n\u001B[1;32m 17\u001B[0m \u001B[38;5;28mprint\u001B[39m(auth_response\u001B[38;5;241m.\u001B[39mtext)\n\u001B[0;32m---> 18\u001B[0m token \u001B[38;5;241m=\u001B[39m \u001B[43mauth_response\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mjson\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m[\u001B[38;5;124m'\u001B[39m\u001B[38;5;124maccess_token\u001B[39m\u001B[38;5;124m'\u001B[39m] \u001B[38;5;66;03m# Adjust based on response format\u001B[39;00m\n\u001B[1;32m 20\u001B[0m \u001B[38;5;66;03m# Setup SOAP client with token\u001B[39;00m\n\u001B[1;32m 21\u001B[0m session \u001B[38;5;241m=\u001B[39m Session()\n", - "File \u001B[0;32m/opt/homebrew/Caskroom/miniconda/base/envs/lappis/lib/python3.11/site-packages/requests/models.py:978\u001B[0m, in \u001B[0;36mResponse.json\u001B[0;34m(self, **kwargs)\u001B[0m\n\u001B[1;32m 974\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m complexjson\u001B[38;5;241m.\u001B[39mloads(\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mtext, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs)\n\u001B[1;32m 975\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m JSONDecodeError \u001B[38;5;28;01mas\u001B[39;00m e:\n\u001B[1;32m 976\u001B[0m \u001B[38;5;66;03m# Catch JSON-related errors and raise as requests.JSONDecodeError\u001B[39;00m\n\u001B[1;32m 977\u001B[0m \u001B[38;5;66;03m# This aliases json.JSONDecodeError and simplejson.JSONDecodeError\u001B[39;00m\n\u001B[0;32m--> 978\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m RequestsJSONDecodeError(e\u001B[38;5;241m.\u001B[39mmsg, e\u001B[38;5;241m.\u001B[39mdoc, e\u001B[38;5;241m.\u001B[39mpos)\n", - "\u001B[0;31mJSONDecodeError\u001B[0m: Expecting value: line 1 column 1 (char 0)" - ] - } - ], - "execution_count": 3 - }, - { - "metadata": { - "ExecuteTime": { - "end_time": "2025-01-27T07:12:44.796378Z", - "start_time": "2025-01-27T07:12:43.165381Z" - } - }, - "cell_type": "code", - "source": [ - "#SIAFI\n", - "\n", - "BEARER_ENDPOINT = \"https://gateway.apiserpro.serpro.gov.br/token\"\n", - "BEARER_KEY = \"fiuUXaOm9JFdKSxnfvF4dJzP0gwa\"\n", - "BEARER_SECRET = \"TZxX2KOpuJ5o_oPABK1NgUV1X3Ea\"\n", - "\n", - "import requests\n", - "\n", - "def get_token(url, consumer_key, consumer_secret):\n", - " data = {\n", - " 'grant_type': 'client_credentials'\n", - " }\n", - "\n", - " response = requests.post(\n", - " url,\n", - " data=data,\n", - " auth=(consumer_key, consumer_secret)\n", - " )\n", - " return response.json()\n", - "\n", - "token_response = get_token(BEARER_ENDPOINT, BEARER_KEY, BEARER_SECRET)" - ], - "id": "7b4eb0b409c26f98", - "outputs": [], - "execution_count": 17 - }, - { - "metadata": { - "ExecuteTime": { - "end_time": "2025-01-27T07:12:45.386983Z", - "start_time": "2025-01-27T07:12:45.384243Z" - } - }, - "cell_type": "code", - "source": "print(token_response)", - "id": "c866fa793feeaba7", - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'access_token': 'eyJ4NXQiOiJNalJqWkRRMU1EQmtPR1JqWW1Jek9EVmxaRFEzWkdFeU1EVTVabU5rWldVeU9XUmhPRFZpTnciLCJraWQiOiJNRGN5WVdFNU16ZzVaVFJrT1dVME1XWTBNVE0xTkdJMllqbG1OVFZrT0dJd01UVmlORGRpWldJM1pEUmpZVEpsTTJaa05Ua3dNR1F3TWpZeVlXTmxNQV9SUzI1NiIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJhdXRlbnRpa3VzIiwiYXV0IjoiQVBQTElDQVRJT04iLCJhdWQiOiJmaXVVWGFPbTlKRmRLU3huZnZGNGRKelAwZ3dhIiwibmJmIjoxNzM3OTYxMjg3LCJhenAiOiJmaXVVWGFPbTlKRmRLU3huZnZGNGRKelAwZ3dhIiwic2NvcGUiOiJkZWZhdWx0IiwiaXNzIjoiaHR0cHM6XC9cL3B1Ymxpc2hlci5hcGlzZXJwcm8uc2VycHJvLmdvdi5icjo0NDNcL29hdXRoMlwvdG9rZW4iLCJyZWFsbSI6eyJzaWduaW5nX3RlbmFudCI6ImNhcmJvbi5zdXBlciJ9LCJleHAiOjE3Mzc5NjQ4ODcsImlhdCI6MTczNzk2MTI4NywianRpIjoiZTA3ZTZmZjEtMzcwNy00OWU4LTg5YzMtZThjODI5NTk3NDU5In0.F7mJhJJOU-nMrsfs9fUXQjw3TpgCCjoIOhhVIujoL52y1d479t0BtQ6KXSOPVVEN4Xf5rjgDlbreNHTcqSK_iBrBPI7uBrSBjn5-li1eXXWVzO7i-w0LuLfYNP30wGjHa9g3D_CoUgyGsSkD_zQ0C6VI24Y0jNQcT_s2dvZ5D97xdZZK_Nu4u8jf4Oz9EX0lfP8mkYubHWk6QDt7KZtXnaoQQZXlZyEwfoymTwd6j8Rc2PquxwefeIPxl1OSw6rGC61KO2G97CmtADmXj-2IqbCj57UHJop3RFXqSh1whKSLThejB4JIUOfoJT6T5L_YkGd8jnxNH9bTuvDGqPlm4A', 'scope': 'default', 'token_type': 'Bearer', 'expires_in': 2922}\n" - ] - } - ], - "execution_count": 18 - }, - { - "metadata": { - "ExecuteTime": { - "end_time": "2025-01-27T07:38:09.493043Z", - "start_time": "2025-01-27T07:38:08.711518Z" - } - }, - "cell_type": "code", - "source": [ - "SIAFI_ENDPOINT = \"https://gateway.apiserpro.serpro.gov.br/api-integra-siafi/api/v2/nota-credito/113601/61201/2024/596765\"\n", - "SIAFI_CREDENTIAL = \"MjE1NTQyMDY4NDcuMTEzNjAxLlNJQUZJMjAyMw==\"\n", - "def get_data(url, bearer_token, credential):\n", - " headers = {\n", - " 'Authorization': f'Bearer {bearer_token}',\n", - " 'Content-Type': 'application/json',\n", - " 'x-credencial': credential,\n", - " }\n", - " response = requests.get(url, headers=headers)\n", - " print(response.text)\n", - " return response.json()\n", - "\n", - "data = get_data(SIAFI_ENDPOINT, token_response.get('access_token'), SIAFI_CREDENTIAL)\n", - "print(data)" - ], - "id": "69cf00b09543d49b", - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{\"codigo\":99,\"mensagem\":\"Requisição inválida\",\"erros\":[{\"code\":\"99\",\"value\":\"\"}],\"status\":400}\n", - "{'codigo': 99, 'mensagem': 'Requisição inválida', 'erros': [{'code': '99', 'value': ''}], 'status': 400}\n" - ] - } - ], - "execution_count": 58 - }, - { - "metadata": {}, - "cell_type": "code", - "outputs": [], - "execution_count": null, - "source": [ - "from datetime import date\n", - "import time\n", - "\n", - "start = date(2024, 1, 1)\n", - "end = date(2025, 1, 1)\n", - "d = start\n", - "while d < end:\n", - " f = format(d, '%d%b%y').upper()\n", - " print(f)\n", - " data = get_data(SIAFI_ENDPOINT.format(date=f), token_response.get('access_token'), SIAFI_CREDENTIAL)\n", - " print(data)\n", - " time.sleep(10)" - ], - "id": "59bbda0290870f91" - }, - { - "metadata": {}, - "cell_type": "code", - "outputs": [], - "execution_count": null, - "source": "", - "id": "9bcadad5c695538a" - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.6" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} From 91563bb14b54191b7ca40af22aae548d306ad39a Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Sun, 9 Feb 2025 20:37:02 +0000 Subject: [PATCH 007/317] feat(dag): adiciona dag de ingestao dos contratos inativos --- .../contratos_inativos_ingest_dag.py | 51 +++++++++++++++++++ airflow_lappis/helpers/postgres_helpers.py | 28 +++++----- airflow_lappis/plugins/cliente_contratos.py | 28 ++++++++++ 3 files changed, 95 insertions(+), 12 deletions(-) create mode 100644 airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py b/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py new file mode 100644 index 00000000..cfd0fe27 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py @@ -0,0 +1,51 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from ...helpers.postgres_helpers import get_postgres_conn +from ...plugins.cliente_contratos import ClienteContratos +from ...plugins.cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Davi", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["contratos_inativos_api"], +) +def api_contratos_inativos_dag() -> None: + """DAG para buscar e armazenar contratos inativos de uma API no PostgreSQL.""" + + @task + def fetch_and_store_contratos_inativos() -> None: + logging.info("Starting fetch_and_store_contratos task") + api = ClienteContratos() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + ug_codes = [113601, 113602] + + for ug_code in ug_codes: + logging.info(f"Fetching contratos for UG code: {ug_code}") + contratos = api.get_contratos_inativos_by_ug(ug_code) + if contratos: + logging.info( + f"Inserting contratos for UG code: " f"{ug_code} into PostgreSQL" + ) + db.insert_data( + contratos, + "contratos_inativos", + conflict_fields=["id"], + primary_key=["id"], + schema="compras_gov", + ) + else: + logging.warning(f"No contratos found for UG code: {ug_code}") + + fetch_and_store_contratos_inativos() + + +dag_instance = api_contratos_inativos_dag() diff --git a/airflow_lappis/helpers/postgres_helpers.py b/airflow_lappis/helpers/postgres_helpers.py index 01b9dfe1..50b287eb 100644 --- a/airflow_lappis/helpers/postgres_helpers.py +++ b/airflow_lappis/helpers/postgres_helpers.py @@ -3,15 +3,19 @@ def get_postgres_conn() -> str: - hook = PostgresHook(postgres_conn_id="postgres_default") - conn = hook.connection - port = conn.port - schema = conn.schema - logging.info( - f"[empenhos_tesouro_ingest_dag.py] Obtained PostgreSQL connection: " - f"dbname={schema}, user={conn.login}, host={conn.host}, port={port}" - ) - return ( - f"dbname={schema} user={conn.login} password={conn.password} " - f"host={conn.host} port={port}" - ) + try: + hook = PostgresHook(postgres_conn_id="postgres_default") + conn = hook.get_conn() + schema = conn.info.dbname + logging.info( + f"[postgres_helpers] Obtained PostgreSQL connection: " + f"dbname={schema}, user={conn.info.user}," + f"host={conn.info.host}, port={conn.info.port}" + ) + return ( + f"dbname={schema} user={conn.info.user} password={conn.info.password} " + f"host={conn.info.host} port={conn.info.port}" + ) + except Exception as e: + logging.error(f"Failed to obtain PostgreSQL connection: {e}") + raise diff --git a/airflow_lappis/plugins/cliente_contratos.py b/airflow_lappis/plugins/cliente_contratos.py index 4a9aa477..04f3d68a 100644 --- a/airflow_lappis/plugins/cliente_contratos.py +++ b/airflow_lappis/plugins/cliente_contratos.py @@ -42,6 +42,34 @@ def get_contratos_by_ug(self, ug_code: str) -> list | None: ) return None + def get_contratos_inativos_by_ug(self, ug_code: str) -> list | None: + """ + Obter todos os contratos inativos de uma UG específica. + + Args: + ug_code (str): UG code + + Returns: + list: lista de contratos inativos por ug + """ + endpoint = f"/contrato/inativo/ug/{ug_code}" + logging.info(f"[cliente_contratos.py] Fetching contratos for UG code: {ug_code}") + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER + ) + if status == http.HTTPStatus.OK and isinstance(data, list): + logging.info( + "[cliente_contratos.py] Successfully fetched contratos for UG code: " + f"{ug_code}" + ) + return data + else: + logging.warning( + "[cliente_contratos.py] Failed to fetch contratos for UG code: " + f"{ug_code} with status: {status}" + ) + return None + def get_faturas_by_contrato_id(self, contrato_id: str) -> list | None: """ Obter todas as faturas de um contrato específico. From 7750741dc5d4964c22d5be0ffdba819d8529c577 Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Sun, 9 Feb 2025 20:38:42 +0000 Subject: [PATCH 008/317] fix(insert) --- airflow_lappis/plugins/cliente_postgres.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/airflow_lappis/plugins/cliente_postgres.py b/airflow_lappis/plugins/cliente_postgres.py index 41855c03..6dc1690b 100644 --- a/airflow_lappis/plugins/cliente_postgres.py +++ b/airflow_lappis/plugins/cliente_postgres.py @@ -138,7 +138,8 @@ def insert_data( if conflict_fields: conflict_str = ", ".join(conflict_fields) - sql += f" ON CONFLICT ({conflict_str}) DO NOTHING" + update_str = ", ".join([f"{col} = EXCLUDED.{col}" for col in columns]) + sql += f" ON CONFLICT ({conflict_str}) DO UPDATE SET {update_str}" with psycopg2.connect(self.conn_str) as conn: with conn.cursor() as cursor: From db2993584e6c866de3c43a15ff0961569f274671 Mon Sep 17 00:00:00 2001 From: Arthur Melo Date: Mon, 10 Feb 2025 15:50:52 +0000 Subject: [PATCH 009/317] =?UTF-8?q?fix(ci):=20corrige=20erro=20de=20import?= =?UTF-8?q?a=C3=A7=C3=A3o=20do=20mypath=20no=20ci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 2 +- Makefile | 2 +- .../dags/data_ingest/contratos_inativos_ingest_dag.py | 6 +++--- airflow_lappis/dags/data_ingest/contratos_ingest_dag.py | 6 +++--- airflow_lappis/dags/data_ingest/cronograma_ingest_dag.py | 6 +++--- airflow_lappis/dags/data_ingest/empenhos_ingest_dag.py | 6 +++--- .../dags/data_ingest/empenhos_tesouro_ingest_dag.py | 6 +++--- .../dags/data_ingest/estagios_tesouro_ingest_dag.py | 6 +++--- airflow_lappis/dags/data_ingest/faturas_ingest_dag.py | 6 +++--- .../dags/data_ingest/unidade_organizacional_ingest_dag.py | 4 ++-- airflow_lappis/plugins/cliente_contratos.py | 2 +- airflow_lappis/plugins/cliente_estrutura.py | 2 +- airflow_lappis/plugins/cliente_siafi.py | 2 +- docker-compose.yml | 7 ++++--- 14 files changed, 32 insertions(+), 31 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7993d7ec..46cb39e4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,7 +2,7 @@ image: python:3.11-slim variables: PYTHONPATH: "${CI_PROJECT_DIR}/dags:${CI_PROJECT_DIR}/plugins:./airflow/dags:./airflow/plugins" - MYPYPATH: "${CI_PROJECT_DIR}/plugins" + MYPYPATH: "airflow_lappis/plugins:airflow_lappis/helpers:airflow_lappis/dags" POETRY_HOME: "/opt/poetry" POETRY_VERSION: "1.8.5" POETRY_VIRTUALENVS_IN_PROJECT: "true" diff --git a/Makefile b/Makefile index 730ee6ff..432c929e 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ format: lint: poetry run black . --check poetry run ruff check . - poetry run mypy . + poetry run mypy . --explicit-package-bases --exclude 'airflow_lappis/helpers/__init__.py|airflow_lappis/plugins/__init__.py' poetry run sqlfmt ./dbt --check poetry run sqlfluff lint ./dbt diff --git a/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py b/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py index cfd0fe27..e1e67b43 100644 --- a/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py @@ -1,9 +1,9 @@ import logging from airflow.decorators import dag, task from datetime import datetime, timedelta -from ...helpers.postgres_helpers import get_postgres_conn -from ...plugins.cliente_contratos import ClienteContratos -from ...plugins.cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn +from cliente_contratos import ClienteContratos +from cliente_postgres import ClientPostgresDB @dag( diff --git a/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py b/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py index 322cd79a..d6b27669 100644 --- a/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py @@ -1,9 +1,9 @@ import logging from airflow.decorators import dag, task from datetime import datetime, timedelta -from ...helpers.postgres_helpers import get_postgres_conn -from ...plugins.cliente_contratos import ClienteContratos -from ...plugins.cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn +from cliente_contratos import ClienteContratos +from cliente_postgres import ClientPostgresDB @dag( diff --git a/airflow_lappis/dags/data_ingest/cronograma_ingest_dag.py b/airflow_lappis/dags/data_ingest/cronograma_ingest_dag.py index 568aae13..41913288 100644 --- a/airflow_lappis/dags/data_ingest/cronograma_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/cronograma_ingest_dag.py @@ -1,9 +1,9 @@ import logging from airflow.decorators import dag, task from datetime import datetime, timedelta -from ...helpers.postgres_helpers import get_postgres_conn -from ...plugins.cliente_contratos import ClienteContratos -from ...plugins.cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn +from cliente_contratos import ClienteContratos +from cliente_postgres import ClientPostgresDB @dag( diff --git a/airflow_lappis/dags/data_ingest/empenhos_ingest_dag.py b/airflow_lappis/dags/data_ingest/empenhos_ingest_dag.py index 02b3c748..845dafd3 100644 --- a/airflow_lappis/dags/data_ingest/empenhos_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/empenhos_ingest_dag.py @@ -1,9 +1,9 @@ import logging from airflow.decorators import dag, task from datetime import datetime, timedelta -from ...helpers.postgres_helpers import get_postgres_conn -from ...plugins.cliente_contratos import ClienteContratos -from ...plugins.cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn +from cliente_contratos import ClienteContratos +from cliente_postgres import ClientPostgresDB @dag( diff --git a/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py index 07abc14b..545c3558 100644 --- a/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py @@ -6,9 +6,9 @@ from datetime import datetime, timedelta import logging import json -from ...plugins.cliente_email import fetch_and_process_emails -from ...plugins.cliente_postgres import ClientPostgresDB -from ...helpers.postgres_helpers import get_postgres_conn +from cliente_email import fetch_and_process_emails +from cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn # Configurações básicas da DAG default_args = { diff --git a/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py index fcd4c194..68846824 100644 --- a/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py @@ -7,9 +7,9 @@ import logging import json -from ...helpers.postgres_helpers import get_postgres_conn -from ...plugins.cliente_email import fetch_and_process_emails -from ...plugins.cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn +from cliente_email import fetch_and_process_emails +from cliente_postgres import ClientPostgresDB # Configurações básicas da DAG default_args = { diff --git a/airflow_lappis/dags/data_ingest/faturas_ingest_dag.py b/airflow_lappis/dags/data_ingest/faturas_ingest_dag.py index eba737f8..f3d2526c 100644 --- a/airflow_lappis/dags/data_ingest/faturas_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/faturas_ingest_dag.py @@ -1,9 +1,9 @@ import logging from airflow.decorators import dag, task from datetime import datetime, timedelta -from ...plugins.cliente_contratos import ClienteContratos -from ...plugins.cliente_postgres import ClientPostgresDB -from ...helpers.postgres_helpers import get_postgres_conn +from cliente_contratos import ClienteContratos +from cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn @dag( diff --git a/airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py b/airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py index e340a3b8..324f1c0b 100644 --- a/airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py @@ -2,8 +2,8 @@ from airflow.decorators import dag, task from airflow.providers.postgres.hooks.postgres import PostgresHook from datetime import datetime, timedelta -from ...plugins.cliente_estrutura import ClienteEstrutura -from ...plugins.cliente_postgres import ClientPostgresDB +from cliente_estrutura import ClienteEstrutura +from cliente_postgres import ClientPostgresDB def get_postgres_conn() -> str: diff --git a/airflow_lappis/plugins/cliente_contratos.py b/airflow_lappis/plugins/cliente_contratos.py index 04f3d68a..ef3084d2 100644 --- a/airflow_lappis/plugins/cliente_contratos.py +++ b/airflow_lappis/plugins/cliente_contratos.py @@ -1,6 +1,6 @@ import http import logging -from .cliente_base import ClienteBase +from cliente_base import ClienteBase class ClienteContratos(ClienteBase): diff --git a/airflow_lappis/plugins/cliente_estrutura.py b/airflow_lappis/plugins/cliente_estrutura.py index f0f19b67..7522b657 100644 --- a/airflow_lappis/plugins/cliente_estrutura.py +++ b/airflow_lappis/plugins/cliente_estrutura.py @@ -1,7 +1,7 @@ import http from typing import Optional -from .cliente_base import ClienteBase +from cliente_base import ClienteBase class ClienteEstrutura(ClienteBase): diff --git a/airflow_lappis/plugins/cliente_siafi.py b/airflow_lappis/plugins/cliente_siafi.py index 15129b13..108f0449 100644 --- a/airflow_lappis/plugins/cliente_siafi.py +++ b/airflow_lappis/plugins/cliente_siafi.py @@ -4,7 +4,7 @@ import requests from httpx import HTTPStatusError -from .cliente_base import ClienteBase +from cliente_base import ClienteBase class ClienteSiafi(ClienteBase): diff --git a/docker-compose.yml b/docker-compose.yml index 5c6e4425..50ddf94d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,8 +7,9 @@ x-airflow-common: &airflow-common env_file: - .env volumes: - - ./airflow/dags:${AIRFLOW_HOME}/dags/ - - ./airflow/plugins:${AIRFLOW_HOME}/plugins/ + - ./airflow_lappis/dags:${AIRFLOW_HOME}/dags/ + - ./airflow_lappis/plugins:${AIRFLOW_HOME}/plugins/ + - ./airflow_lappis/helpers:${AIRFLOW_HOME}/helpers/ depends_on: &airflow-common-depends-on postgres: condition: service_healthy @@ -30,7 +31,7 @@ x-airflow-environment: &airflow-common-env AIRFLOW__WEBSERVER__NAVBAR_COLOR: '#98DFFF' AIRFLOW__WEBSERVER__RELOAD_ON_PLUGIN_CHANGE: 'true' AIRFLOW__WEBSERVER__SECRET_KEY: '42' - PYTHONPATH: '${AIRFLOW_HOME}/dags:${AIRFLOW_HOME}/plugins' + PYTHONPATH: '${AIRFLOW_HOME}/dags:${AIRFLOW_HOME}/plugins:${AIRFLOW_HOME}/helpers' _AIRFLOW_DB_MIGRATE: 'true' _AIRFLOW_WWW_USER_CREATE: 'true' _AIRFLOW_WWW_USER_USERNAME: ${_AIRFLOW_WWW_USER_USERNAME:-airflow} From fb157982d24af1f037bac26f7fcb8a2d4cf1eed9 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Tue, 11 Feb 2025 14:53:11 -0300 Subject: [PATCH 010/317] fix(email): ajusta parametro de assunto do email --- airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py | 2 +- airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py index 545c3558..ac425a14 100644 --- a/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py @@ -40,7 +40,7 @@ 18: "despesas_pagas_controle_empenho_movim_liquido_moeda_origem", } -EMAIL_SUBJECT = "consulta_por_execucao_emp_liq_pago" +EMAIL_SUBJECT = "consulta_por_execução_emp_liq_pago" # Configurações da DAG diff --git a/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py index 68846824..4712909d 100644 --- a/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py @@ -43,7 +43,7 @@ 20: "despesas_pagas_controle_empenho_movim_liquido_moeda_origem", } -EMAIL_SUBJECT = "consulta_por_execucao_emp_liq_pago_mensal" +EMAIL_SUBJECT = "consulta_por_execução_emp_liq_pago_mensal" # Configurações da DAG From a089ed1ad4ddb8d0a9f65703c9da373af9f3a893 Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Wed, 12 Feb 2025 14:28:42 +0000 Subject: [PATCH 011/317] feat(trigger): adiciona trigger para ativar a dag de inativos depois dos... --- .../dags/data_ingest/contratos_inativos_ingest_dag.py | 2 +- airflow_lappis/dags/data_ingest/contratos_ingest_dag.py | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py b/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py index e1e67b43..7038c73b 100644 --- a/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py @@ -37,7 +37,7 @@ def fetch_and_store_contratos_inativos() -> None: ) db.insert_data( contratos, - "contratos_inativos", + "contratos", conflict_fields=["id"], primary_key=["id"], schema="compras_gov", diff --git a/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py b/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py index d6b27669..e781fefa 100644 --- a/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py @@ -1,5 +1,6 @@ import logging from airflow.decorators import dag, task +from airflow.operators.trigger_dagrun import TriggerDagRunOperator from datetime import datetime, timedelta from postgres_helpers import get_postgres_conn from cliente_contratos import ClienteContratos @@ -50,7 +51,13 @@ def fetch_and_store_contratos() -> None: f"[contratos_ingest_dag.py] No contratos found for UG code: {ug_code}" ) - fetch_and_store_contratos() + trigger_contratos_inativos = TriggerDagRunOperator( + task_id="trigger_contratos_inativos", + trigger_dag_id="api_contratos_inativos_dag", + wait_for_completion=False, + ) + + fetch_and_store_contratos() >> trigger_contratos_inativos dag_instance = api_contratos_dag() From 72dd8a17d4419ae1e082161aae5bd08f0a0803bd Mon Sep 17 00:00:00 2001 From: VictorSzk Date: Sun, 16 Feb 2025 11:08:14 +0000 Subject: [PATCH 012/317] Fix/org models --- Makefile | 3 +- airflow_lappis/dags/dbt/ipea/dbt_project.yml | 2 + .../dags/dbt/ipea/macros/create_udfs.sql | 8 + .../dbt/ipea/macros/get_custom_schema.sql | 3 +- .../macros/udfs/f_parse_financial_value.sql | 14 + .../models/contratos/bronze/contratos.sql | 213 ++++++---- .../ipea/models/contratos/bronze/empenhos.sql | 88 ++-- .../contratos/bronze/empenhos_tesouro.sql | 75 ++-- .../ipea/models/contratos/bronze/estagios.sql | 145 ++++--- .../ipea/models/contratos/bronze/faturas.sql | 92 ++--- .../contratos/bronze/identificadores.sql | 60 +++ .../contratos/gold/contratos_resumo.sql | 83 ++-- .../contratos/silver/contratos_empenhos.sql | 390 +++++++++++------- .../dags/dbt/ipea/models/sources.yml | 17 + airflow_lappis/dags/dbt/ipea/profiles.yml | 1 + .../dbt/ipea/snapshots/contratos_snapshot.yml | 10 + dbt/__init__.py | 0 dbt_project.yml | 25 -- profiles.yml | 11 - 19 files changed, 744 insertions(+), 496 deletions(-) create mode 100644 airflow_lappis/dags/dbt/ipea/macros/create_udfs.sql create mode 100644 airflow_lappis/dags/dbt/ipea/macros/udfs/f_parse_financial_value.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/contratos/bronze/identificadores.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sources.yml create mode 100644 airflow_lappis/dags/dbt/ipea/snapshots/contratos_snapshot.yml delete mode 100644 dbt/__init__.py delete mode 100644 dbt_project.yml delete mode 100644 profiles.yml diff --git a/Makefile b/Makefile index 432c929e..1d5478fa 100644 --- a/Makefile +++ b/Makefile @@ -17,8 +17,7 @@ lint: poetry run black . --check poetry run ruff check . poetry run mypy . --explicit-package-bases --exclude 'airflow_lappis/helpers/__init__.py|airflow_lappis/plugins/__init__.py' - poetry run sqlfmt ./dbt --check - poetry run sqlfluff lint ./dbt + poetry run sqlfmt ./airflow_lappis/dags/dbt/ipea --check test: poetry run pytest tests diff --git a/airflow_lappis/dags/dbt/ipea/dbt_project.yml b/airflow_lappis/dags/dbt/ipea/dbt_project.yml index 1bf2d907..35338d41 100644 --- a/airflow_lappis/dags/dbt/ipea/dbt_project.yml +++ b/airflow_lappis/dags/dbt/ipea/dbt_project.yml @@ -30,3 +30,5 @@ models: +database: analytics +schema: pessoas +on-run-start: + - '{{create_udfs()}}' \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/ipea/macros/create_udfs.sql b/airflow_lappis/dags/dbt/ipea/macros/create_udfs.sql new file mode 100644 index 00000000..f5f5347c --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/macros/create_udfs.sql @@ -0,0 +1,8 @@ +{% macro create_udfs() %} + +create schema if not exists {{ target.schema }}; + + {{ create_f_parse_financial_value() }} + ; + +{% endmacro %} diff --git a/airflow_lappis/dags/dbt/ipea/macros/get_custom_schema.sql b/airflow_lappis/dags/dbt/ipea/macros/get_custom_schema.sql index 21bc73c8..79444e9a 100644 --- a/airflow_lappis/dags/dbt/ipea/macros/get_custom_schema.sql +++ b/airflow_lappis/dags/dbt/ipea/macros/get_custom_schema.sql @@ -1,5 +1,4 @@ -- built-in schema generator - {% macro generate_schema_name(custom_schema_name, node) -%} {{ generate_schema_name_for_env(custom_schema_name, node) }} -{%- endmacro %} \ No newline at end of file +{%- endmacro %} diff --git a/airflow_lappis/dags/dbt/ipea/macros/udfs/f_parse_financial_value.sql b/airflow_lappis/dags/dbt/ipea/macros/udfs/f_parse_financial_value.sql new file mode 100644 index 00000000..17c58951 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/macros/udfs/f_parse_financial_value.sql @@ -0,0 +1,14 @@ +{% macro create_f_parse_financial_value() %} + + create or replace function {{ target.schema }}.parse_number(in_text text) + returns numeric + as + $$ + select + case when in_text like '(%' then regexp_replace(replace(coalesce(in_text, '0'), '.', ''), '(\()?(\d+),(\d+)(\))?', '-\2.\3')::numeric(15,2) + else replace(replace(coalesce(in_text, '0'), '.', ''), ',', '.')::numeric(15,2) end as result +$$ + language sql + ; + +{% endmacro %} diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/contratos.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/contratos.sql index cabb32f1..f23c1851 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/contratos.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/contratos.sql @@ -1,88 +1,129 @@ +{{ config(materialized="table") }} +with + contratos_raw as ( + select + -- Conversão de tipos e formatação de colunas + cast(id as text) as id, + receita_despesa, + numero, + cast( + contratante_orgao_origem_codigo as int + ) as contratante_orgao_origem_codigo, + contratante_orgao_origem_nome, + cast( + contratante_orgao_origem_unidade_gestora_origem_codigo as int + ) as contratante_orgao_origem_unidade_gestora_origem_codigo, + contratante_orgao_origem_unidade_gestora_origem_nome_resumido, + contratante_orgao_origem_unidade_gestora_origem_nome, + contratante_orgao_origem_unidade_gestora_origem_sisg, + contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi, + contratante_orgao_origem_unidade_gestora_origem_utiliza_antecip, + cast(contratante_orgao_codigo as int) as contratante_orgao_codigo, + contratante_orgao_nome, + cast( + contratante_orgao_unidade_gestora_codigo as int + ) as contratante_orgao_unidade_gestora_codigo, + contratante_orgao_unidade_gestora_nome_resumido, + contratante_orgao_unidade_gestora_nome, + contratante_orgao_unidade_gestora_sisg, + contratante_orgao_unidade_gestora_utiliza_siafi, + contratante_orgao_unidade_gestora_utiliza_antecipagov, + fornecedor_tipo, + fornecedor_nome, + cast(codigo_tipo as int) as codigo_tipo, + tipo, + subtipo, + prorrogavel, + situacao, + justificativa_inativo, + categoria, + subcategoria, + unidades_requisitantes, + objeto, + amparo_legal, + informacao_complementar, + codigo_modalidade, + modalidade, + cast(unidade_compra as int) as unidade_compra, + licitacao_numero, + sistema_origem_licitacao, + cast(num_parcelas as int) as num_parcelas, + regexp_replace( + fornecedor_cnpj_cpf_idgener, '[^0-9A-Za-z]', '', 'g' + ) as fornecedor_cnpj_cpf_idgener, + -- Tratar valores nulos ou inválidos nas colunas de data + regexp_replace(processo, '[^0-9A-Za-z]', '', 'g') as processo, + case + when data_assinatura is null + then null + when + data_assinatura is not null + and cast(data_assinatura as text) ~ '^\d{4}-\d{2}-\d{2}$' + -- Retorna NULL se não for uma data válida + then to_date(cast(data_assinatura as text), 'YYYY-MM-DD') + end as data_assinatura, + case + when data_publicacao is null + then null + when + data_publicacao is not null + and cast(data_publicacao as text) ~ '^\d{4}-\d{2}-\d{2}$' + -- Retorna NULL se não for uma data válida + then to_date(cast(data_publicacao as text), 'YYYY-MM-DD') + end as data_publicacao, + case + when data_proposta_comercial is null + then null + when + data_proposta_comercial is not null + and cast(data_proposta_comercial as text) ~ '^\d{4}-\d{2}-\d{2}$' + then + -- Retorna NULL se não for uma data válida + to_date(cast(data_proposta_comercial as text), 'YYYY-MM-DD') + end as data_proposta_comercial, + case + when vigencia_inicio is null + then null + when + vigencia_inicio is not null + and cast(vigencia_inicio as text) ~ '^\d{4}-\d{2}-\d{2}$' + -- Retorna NULL se não for uma data válida + then to_date(cast(vigencia_inicio as text), 'YYYY-MM-DD') + end as vigencia_inicio, + -- Conversão de valores numéricos para FLOAT ou INT + case + when vigencia_fim is null + then null + when + vigencia_fim is not null + and cast(vigencia_fim as text) ~ '^\d{4}-\d{2}-\d{2}$' + -- Retorna NULL se não for uma data válida + then to_date(cast(vigencia_fim as text), 'YYYY-MM-DD') + end as vigencia_fim, + cast( + replace( + replace(cast(valor_inicial as text), '.', ''), ',', '.' + ) as numeric(15, 2) + ) as valor_inicial, + cast( + replace( + replace(cast(valor_global as text), '.', ''), ',', '.' + ) as numeric(15, 2) + ) as valor_global, + cast( + replace( + replace(cast(valor_parcela as text), '.', ''), ',', '.' + ) as numeric(15, 2) + ) as valor_parcela, + cast( + replace( + replace(cast(valor_acumulado as text), '.', ''), ',', '.' + ) as numeric(15, 2) + ) as valor_acumulado, + now() as updated_at + from {{ source("compras_gov", "contratos") }} + ) -- -WITH contratos_raw AS ( - SELECT - -- Conversão de tipos e formatação de colunas - CAST(id AS INT) AS id, - receita_despesa, - numero, - CAST(contratante_orgao_origem_codigo AS INT) AS contratante_orgao_origem_codigo, - contratante_orgao_origem_nome, - CAST(contratante_orgao_origem_unidade_gestora_origem_codigo AS INT) AS contratante_orgao_origem_unidade_gestora_origem_codigo, - contratante_orgao_origem_unidade_gestora_origem_nome_resumido, - contratante_orgao_origem_unidade_gestora_origem_nome, - contratante_orgao_origem_unidade_gestora_origem_sisg, - contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi, - contratante_orgao_origem_unidade_gestora_origem_utiliza_antecip, - CAST(contratante_orgao_codigo AS INT) AS contratante_orgao_codigo, - contratante_orgao_nome, - CAST(contratante_orgao_unidade_gestora_codigo AS INT) AS contratante_orgao_unidade_gestora_codigo, - contratante_orgao_unidade_gestora_nome_resumido, - contratante_orgao_unidade_gestora_nome, - contratante_orgao_unidade_gestora_sisg, - contratante_orgao_unidade_gestora_utiliza_siafi, - contratante_orgao_unidade_gestora_utiliza_antecipagov, - fornecedor_tipo, - REGEXP_REPLACE(fornecedor_cnpj_cpf_idgener, '[^0-9A-Za-z]', '', 'g') AS fornecedor_cnpj_cpf_idgener, - fornecedor_nome, - CAST(codigo_tipo AS INT) AS codigo_tipo, - tipo, - subtipo, - prorrogavel, - situacao, - justificativa_inativo, - categoria, - subcategoria, - unidades_requisitantes, - REGEXP_REPLACE(processo, '[^0-9A-Za-z]', '', 'g') AS processo, - objeto, - amparo_legal, - informacao_complementar, - codigo_modalidade, - modalidade, - CAST(unidade_compra AS INT) AS unidade_compra, - licitacao_numero, - sistema_origem_licitacao, - - -- Tratar valores nulos ou inválidos nas colunas de data - CASE - WHEN data_assinatura IS NULL THEN NULL - WHEN data_assinatura IS NOT NULL AND data_assinatura::text ~ '^\d{4}-\d{2}-\d{2}$' THEN TO_DATE(data_assinatura::text, 'YYYY-MM-DD') - ELSE NULL -- Retorna NULL se não for uma data válida - END AS data_assinatura, - - CASE - WHEN data_publicacao IS NULL THEN NULL - WHEN data_publicacao IS NOT NULL AND data_publicacao::text ~ '^\d{4}-\d{2}-\d{2}$' THEN TO_DATE(data_publicacao::text, 'YYYY-MM-DD') - ELSE NULL -- Retorna NULL se não for uma data válida - END AS data_publicacao, - - CASE - WHEN data_proposta_comercial IS NULL THEN NULL - WHEN data_proposta_comercial IS NOT NULL AND data_proposta_comercial::text ~ '^\d{4}-\d{2}-\d{2}$' THEN TO_DATE(data_proposta_comercial::text, 'YYYY-MM-DD') - ELSE NULL -- Retorna NULL se não for uma data válida - END AS data_proposta_comercial, - - CASE - WHEN vigencia_inicio IS NULL THEN NULL - WHEN vigencia_inicio IS NOT NULL AND vigencia_inicio::text ~ '^\d{4}-\d{2}-\d{2}$' THEN TO_DATE(vigencia_inicio::text, 'YYYY-MM-DD') - ELSE NULL -- Retorna NULL se não for uma data válida - END AS vigencia_inicio, - - CASE - WHEN vigencia_fim IS NULL THEN NULL - WHEN vigencia_fim IS NOT NULL AND vigencia_fim::text ~ '^\d{4}-\d{2}-\d{2}$' THEN TO_DATE(vigencia_fim::text, 'YYYY-MM-DD') - ELSE NULL -- Retorna NULL se não for uma data válida - END AS vigencia_fim, - - -- Conversão de valores numéricos para FLOAT ou INT - REPLACE(REPLACE(valor_inicial::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS valor_inicial, - REPLACE(REPLACE(valor_global::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS valor_global, - CAST(num_parcelas AS INT) AS num_parcelas, - REPLACE(REPLACE(valor_parcela::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS valor_parcela, - REPLACE(REPLACE(valor_acumulado::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS valor_acumulado, - - FROM raw.contratos -) - -SELECT * FROM contratos_raw +select * +from contratos_raw diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos.sql index ace9d56b..6c8a5d5d 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos.sql @@ -1,38 +1,56 @@ -WITH empenhos AS ( - SELECT - id::TEXT AS id, - contrato_id::TEXT AS contrato_id, - unidade_gestora, - gestao, - numero AS nota_empenho, - credor, - fonte_recurso, - programa_trabalho, - planointerno, - naturezadespesa, - informacao_complementar, - sistema_origem, - links_documento_pagamento, - credor_obj_tipo, - credor_obj_cnpj_cpf_idgener, - credor_obj_nome, +with + empenhos as ( + select + id::text as id, + contrato_id::text as contrato_id, + unidade_gestora, + gestao, + numero as nota_empenho, + credor, + fonte_recurso, + programa_trabalho, + planointerno, + naturezadespesa, + informacao_complementar, + sistema_origem, + links_documento_pagamento, + credor_obj_tipo, + credor_obj_cnpj_cpf_idgener, + credor_obj_nome, - -- Tratar valores nulos ou inválidos nas colunas de data - CASE - WHEN data_emissao IS NOT NULL AND data_emissao::text ~ '^\d{4}-\d{2}-\d{2}$' THEN TO_DATE(data_emissao::text, 'YYYY-MM-DD') - ELSE NULL -- Retorna NULL se não for uma data válida - END AS data_emissao, + -- Tratar valores nulos ou inválidos nas colunas de data + replace(replace(empenhado::text, '.', ''), ',', '.')::numeric( + 15, 2 + ) as empenhado, - -- Conversão de valores numéricos para FLOAT - REPLACE(REPLACE(empenhado::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS empenhado, - REPLACE(REPLACE(aliquidar::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS aliquidar, - REPLACE(REPLACE(liquidado::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS liquidado, - REPLACE(REPLACE(pago::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS pago, - REPLACE(REPLACE(rpinscrito::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS rpinscrito, - REPLACE(REPLACE(rpaliquidar::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS rpaliquidar, - REPLACE(REPLACE(rpliquidado::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS rpliquidado, - REPLACE(REPLACE(rppago::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS rppago + -- Conversão de valores numéricos para FLOAT + replace(replace(aliquidar::text, '.', ''), ',', '.')::numeric( + 15, 2 + ) as aliquidar, + replace(replace(liquidado::text, '.', ''), ',', '.')::numeric( + 15, 2 + ) as liquidado, + replace(replace(pago::text, '.', ''), ',', '.')::numeric(15, 2) as pago, + replace(replace(rpinscrito::text, '.', ''), ',', '.')::numeric( + 15, 2 + ) as rpinscrito, + replace(replace(rpaliquidar::text, '.', ''), ',', '.')::numeric( + 15, 2 + ) as rpaliquidar, + replace(replace(rpliquidado::text, '.', ''), ',', '.')::numeric( + 15, 2 + ) as rpliquidado, + replace(replace(rppago::text, '.', ''), ',', '.')::numeric(15, 2) as rppago, + case + when + data_emissao is not null + and data_emissao::text ~ '^\d{4}-\d{2}-\d{2}$' + -- Retorna NULL se não for uma data válida + then to_date(data_emissao::text, 'YYYY-MM-DD') + end as data_emissao - FROM raw.empenhos -) -SELECT * FROM empenhos \ No newline at end of file + from {{ source("compras_gov", "empenhos") }} + ) + +select * +from empenhos diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql index 98214e2d..3dea10af 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql @@ -1,36 +1,43 @@ -create or replace function parse_number(in_text text) returns numeric as $$ - select - case when in_text like '(%' then regexp_replace(replace(coalesce(in_text, '0'), '.', ''), '(\()?(\d+),(\d+)(\))?', '-\2.\3')::numeric(15,2) - else replace(replace(coalesce(in_text, '0'), '.', ''), ',', '.')::numeric(15,2) end as result -$$ language sql; +with -WITH + empenhos_raw as ( + select + id::integer as id, + ne_ccor::text as ne_ccor, + ne_informacao_complementar::text as ne_informacao_complementar, + regexp_replace(ne_num_processo, '[./-]', '') as ne_num_processo, + ne_ccor_descricao::text as ne_ccor_descricao, + doc_observacao::text as doc_observacao, + natureza_despesa::integer as natureza_despesa, + natureza_despesa_1::text as natureza_despesa_1, + natureza_despesa_detalhada::integer as natureza_despesa_detalhada, + natureza_despesa_detalhada_1::text as natureza_despesa_detalhada_1, + ne_ccor_favorecido::text as ne_ccor_favorecido, + ne_ccor_favorecido_1::text as ne_ccor_favorecido_1, + ne_ccor_ano_emissao::integer as ne_ccor_ano_emissao, + item_informacao::integer as ne_ccor_ano_emissao_1, + -- Aplicando NULLIF e removendo parênteses antes de converter para NUMERIC + {{ target.schema }}.parse_number( + despesas_empenhadas_controle_empenho_saldo_moeda_origem + ) as despesas_empenhadas_saldo, + {{ target.schema }}.parse_number( + despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem + ) as despesas_empenhadas_movim_liquido, + {{ target.schema }}.parse_number( + despesas_liquidadas_controle_empenho_saldo_moeda_origem + ) as despesas_liquidadas_saldo, + {{ target.schema }}.parse_number( + despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem + ) as despesas_liquidadas_movim_liquido, + {{ target.schema }}.parse_number( + despesas_pagas_controle_empenho_saldo_moeda_origem + ) as despesas_pagas_saldo, + {{ target.schema }}.parse_number( + despesas_pagas_controle_empenho_movim_liquido_moeda_origem + ) as despesas_pagas_movim_liquido + from {{ source("siafi", "empenhos_tesouro") }} + where ne_ccor != 'Total' + ) -empenhos_raw AS ( - SELECT - id::INTEGER AS id, - ne_ccor::TEXT AS ne_ccor, - ne_informacao_complementar::TEXT AS ne_informacao_complementar, - REGEXP_REPLACE(ne_num_processo, '[./-]', '') AS ne_num_processo, - ne_ccor_descricao::TEXT AS ne_ccor_descricao, - doc_observacao::TEXT AS doc_observacao, - natureza_despesa::INTEGER AS natureza_despesa, - natureza_despesa_1::TEXT AS natureza_despesa_1, - natureza_despesa_detalhada::INTEGER AS natureza_despesa_detalhada, - natureza_despesa_detalhada_1::TEXT AS natureza_despesa_detalhada_1, - ne_ccor_favorecido::TEXT AS ne_ccor_favorecido, - ne_ccor_favorecido_1::TEXT AS ne_ccor_favorecido_1, - ne_ccor_ano_emissao::INTEGER AS ne_ccor_ano_emissao, - item_informacao::INTEGER AS ne_ccor_ano_emissao_1, - -- Aplicando NULLIF e removendo parênteses antes de converter para NUMERIC - parse_number(despesas_empenhadas_controle_empenho_saldo_moeda_origem) AS despesas_empenhadas_saldo, - parse_number(despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem) AS despesas_empenhadas_movim_liquido, - parse_number(despesas_liquidadas_controle_empenho_saldo_moeda_origem) AS despesas_liquidadas_saldo, - parse_number(despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem) AS despesas_liquidadas_movim_liquido, - parse_number(despesas_pagas_controle_empenho_saldo_moeda_origem) AS despesas_pagas_saldo, - parse_number(despesas_pagas_controle_empenho_movim_liquido_moeda_origem) AS despesas_pagas_movim_liquido - FROM raw.empenhos_tesouro - WHERE ne_ccor != 'Total' -) - -SELECT * FROM empenhos_raw \ No newline at end of file +select * +from empenhos_raw diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/estagios.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/estagios.sql index 8defa971..3e31bc19 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/estagios.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/estagios.sql @@ -1,10 +1,5 @@ -create or replace function parse_number(in_text text) returns numeric as $$ - select - case when in_text like '(%' then regexp_replace(replace(coalesce(in_text, '0'), '.', ''), '(\()?(\d+),(\d+)(\))?', '-\2.\3')::numeric(15,2) - else replace(replace(coalesce(in_text, '0'), '.', ''), ',', '.')::numeric(15,2) end as result -$$ language sql; - --- Comentário: O erro indica que há valores com parênteses "(660000.00)" que não podem ser convertidos para double precision +-- Comentário: O erro indica que há valores com parênteses "(660000.00)" que não podem +-- ser convertidos para double precision -- O erro está ocorrendo nas colunas de valores monetários, especificamente em: -- - despesas_empenhadas_controle_empenho_saldo_moeda_origem -- - despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem @@ -12,63 +7,79 @@ $$ language sql; -- - despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem -- - despesas_pagas_controle_empenho_saldo_moeda_origem -- - despesas_pagas_controle_empenho_movim_liquido_moeda_origem - --- Precisamos modificar o CAST dessas colunas para tratar valores entre parênteses como números negativos - - - -WITH estagios_raw AS ( - SELECT - id::INTEGER as id, - ne_ccor, - ne_informacao_complementar :: TEXT, - - -- Remove o "0" inicial e pontos, barras e hífens do número do processo - CASE - WHEN LENGTH(ne_num_processo::text) > 3 THEN REGEXP_REPLACE(LTRIM(ne_num_processo::text, '0'), '[\./-]', '', 'g') - ELSE ne_num_processo::text - END AS ne_num_processo, - - ne_ccor_descricao :: TEXT, - doc_observacao :: TEXT, - - CASE - WHEN natureza_despesa::text ~ '^\d+$' THEN CAST(natureza_despesa AS INTEGER) - ELSE NULL - END AS natureza_despesa, - - natureza_despesa_1, - - CASE - WHEN natureza_despesa_detalhada::text ~ '^\d+$' THEN CAST(natureza_despesa_detalhada AS INTEGER) - ELSE NULL - END AS natureza_despesa_detalhada, - - natureza_despesa_detalhada_1, - ne_ccor_favorecido, - ne_ccor_favorecido_1, - - CASE - WHEN ano_lancamento::text ~ '^\d+$' THEN CAST(ano_lancamento AS INTEGER) - ELSE NULL - END AS ano_lancamento, - - ne_ccor_mes_emissao, - - CASE - WHEN ne_ccor_ano_emissao::text ~ '^\d+$' THEN CAST(ne_ccor_ano_emissao AS INTEGER) - ELSE NULL - END AS ne_ccor_ano_emissao, - - mes_lancamento, - parse_number(despesas_empenhadas_controle_empenho_saldo_moeda_origem) AS despesas_empenhadas_controle_empenho_saldo_moeda_origem, - parse_number(despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem) AS despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem, - parse_number(despesas_liquidadas_controle_empenho_saldo_moeda_origem) AS despesas_liquidadas_controle_empenho_saldo_moeda_origem, - parse_number(despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem) AS despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem, - parse_number(despesas_pagas_controle_empenho_saldo_moeda_origem) AS despesas_pagas_controle_empenho_saldo_moeda_origem, - parse_number(despesas_pagas_controle_empenho_movim_liquido_moeda_origem) AS despesas_pagas_controle_empenho_movim_liquido_moeda_origem - - FROM raw.estagios -) - -SELECT * FROM estagios_raw +-- Precisamos modificar o CAST dessas colunas para tratar valores entre parênteses como +-- números negativos +with + estagios_raw as ( + select + id::integer as id, + ne_ccor, + ne_informacao_complementar::text, + + -- Remove o "0" inicial e pontos, barras e hífens do número do processo + case + when length(ne_num_processo::text) > 3 + then regexp_replace(ltrim(ne_num_processo::text, '0'), '[\./-]', '', 'g') + else ne_num_processo::text + end as ne_num_processo, + + ne_ccor_descricao::text, + doc_observacao::text, + + case + when natureza_despesa::text ~ '^\d+$' + then cast(natureza_despesa as integer) + else null + end as natureza_despesa, + + natureza_despesa_1, + + case + when natureza_despesa_detalhada::text ~ '^\d+$' + then cast(natureza_despesa_detalhada as integer) + else null + end as natureza_despesa_detalhada, + + natureza_despesa_detalhada_1, + ne_ccor_favorecido, + ne_ccor_favorecido_1, + + case + when ano_lancamento::text ~ '^\d+$' + then cast(ano_lancamento as integer) + else null + end as ano_lancamento, + + ne_ccor_mes_emissao, + + case + when ne_ccor_ano_emissao::text ~ '^\d+$' + then cast(ne_ccor_ano_emissao as integer) + else null + end as ne_ccor_ano_emissao, + + mes_lancamento, + {{ target.schema }}.parse_number( + despesas_empenhadas_controle_empenho_saldo_moeda_origem + ) as despesas_empenhadas_controle_empenho_saldo_moeda_origem, + {{ target.schema }}.parse_number( + despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem + ) as despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem, + {{ target.schema }}.parse_number( + despesas_liquidadas_controle_empenho_saldo_moeda_origem + ) as despesas_liquidadas_controle_empenho_saldo_moeda_origem, + {{ target.schema }}.parse_number( + despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem + ) as despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem, + {{ target.schema }}.parse_number( + despesas_pagas_controle_empenho_saldo_moeda_origem + ) as despesas_pagas_controle_empenho_saldo_moeda_origem, + {{ target.schema }}.parse_number( + despesas_pagas_controle_empenho_movim_liquido_moeda_origem + ) as despesas_pagas_controle_empenho_movim_liquido_moeda_origem + + from {{ source("siafi", "estagios") }} + ) + +select * +from estagios_raw diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/faturas.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/faturas.sql index 1fdccc08..e467ac15 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/faturas.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/faturas.sql @@ -1,49 +1,49 @@ +with + faturas_raw as ( + select + id::integer as id, + contrato_id::integer as contrato_id, + tipolistafatura_id::text as tipolistafatura_id, + justificativafatura_id::text as justificativafatura_id, + sfadrao_id::text as sfadrao_id, + numero::text as numero, + emissao::date as emissao, + prazo::date as prazo, + vencimento::date as vencimento, + -- Limpar o formato numérico das colunas que têm problemas + replace(replace(valor::text, '.', ''), ',', '.')::numeric(15, 2) as valor, + replace(replace(juros::text, '.', ''), ',', '.')::numeric(15, 2) as juros, + replace(replace(multa::text, '.', ''), ',', '.')::numeric(15, 2) as multa, + replace(replace(glosa::text, '.', ''), ',', '.')::numeric(15, 2) as glosa, + replace(replace(valorliquido::text, '.', ''), ',', '.')::numeric( + 15, 2 + ) as valorliquido, + processo::text as processo, + protocolo::date as protocolo, + ateste::date as ateste, + repactuacao::text as repactuacao, + infcomplementar::text as infcomplementar, + mesref::integer as mesref, + anoref::integer as anoref, + situacao::text as situacao, + chave_nfe::text as chave_nfe, + dados_referencia::text as dados_referencia, + dados_item_faturado::text as dados_item_faturado, + jsonb_array_elements(dados_empenho::jsonb) as dados_empenho + from {{ source("compras_gov", "faturas") }} + ), - -WITH faturas_raw AS ( - SELECT - id::INTEGER AS id, - contrato_id::INTEGER AS contrato_id, - tipolistafatura_id::TEXT AS tipolistafatura_id, - justificativafatura_id::TEXT AS justificativafatura_id, - sfadrao_id::TEXT AS sfadrao_id, - numero::TEXT AS numero, - emissao::DATE AS emissao, - prazo::DATE AS prazo, - vencimento::DATE AS vencimento, - -- Limpar o formato numérico das colunas que têm problemas - REPLACE(REPLACE(valor::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS valor, - REPLACE(REPLACE(juros::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS juros, - REPLACE(REPLACE(multa::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS multa, - REPLACE(REPLACE(glosa::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS glosa, - REPLACE(REPLACE(valorliquido::TEXT, '.', ''), ',', '.')::NUMERIC(15, 2) AS valorliquido, - processo::TEXT AS processo, - protocolo::DATE AS protocolo, - ateste::DATE AS ateste, - repactuacao::TEXT AS repactuacao, - infcomplementar::TEXT AS infcomplementar, - mesref::INTEGER AS mesref, - anoref::INTEGER AS anoref, - situacao::TEXT AS situacao, - chave_nfe::TEXT AS chave_nfe, - jsonb_array_elements(dados_empenho::jsonb) AS dados_empenho, - dados_referencia::TEXT AS dados_referencia, - dados_item_faturado::TEXT AS dados_item_faturado - FROM raw.faturas -), - --- Extrai os campos do JSON e transforma em colunas individuais -faturas_dados_empenho AS ( - SELECT - f.*, - dados_empenho->>'id_empenho' AS id_empenho, - upper(dados_empenho->>'numero_empenho') AS numero_empenho, - dados_empenho->>'valor_empenho' AS valor_empenho, - dados_empenho->>'subelemento' AS subelemento - FROM faturas_raw AS f -) + -- Extrai os campos do JSON e transforma em colunas individuais + faturas_dados_empenho as ( + select + f.*, + f.dados_empenho ->> 'id_empenho' as id_empenho, + upper(f.dados_empenho ->> 'numero_empenho') as numero_empenho, + f.dados_empenho ->> 'valor_empenho' as valor_empenho, + f.dados_empenho ->> 'subelemento' as subelemento + from faturas_raw as f + ) -- - -SELECT * -FROM faturas_dados_empenho +select * +from faturas_dados_empenho diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/identificadores.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/identificadores.sql new file mode 100644 index 00000000..80e195d8 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/identificadores.sql @@ -0,0 +1,60 @@ +with + + ids_from_empenhos as ( + select distinct contrato_id::text as contrato_id, upper(nota_empenho) as ne + from {{ ref("empenhos") }} + ), + + ids_from_faturas as ( + select distinct contrato_id::text as contrato_id, upper(numero_empenho) as ne + from {{ ref("faturas") }} + ), + + ids_table as ( + select contrato_id, ne + from ids_from_empenhos + full join + ids_from_faturas + on ids_from_empenhos.contrato_id = ids_from_faturas.contrato_id + and ids_from_empenhos.ne = ids_from_faturas.ne + ), + + contratos as ( + select + id::text as contrato_id, + case when length(numero) = 12 then numero end as ne, + regexp_replace(processo, '[^0-9]', '', 'g') as processo, + regexp_replace(fornecedor_cnpj_cpf_idgener, '[/.-]', '', 'g') as cnpj_cpf, + case + when codigo_modalidade in ('05', '06') + then + concat( + contratante_orgao_unidade_gestora_codigo, + codigo_modalidade, + replace(numero, '/', '') + ) + when codigo_modalidade = '07' + then + concat( + contratante_orgao_unidade_gestora_codigo, + codigo_modalidade, + replace(licitacao_numero, '/', '') + ) + end as info_complementar + from {{ ref("contratos") }} + ), + + identificadores as ( + select + c.contrato_id, + c.processo, + c.cnpj_cpf, + c.info_complementar, + coalesce(i.ne, c.ne) as ne + from contratos as c + full join ids_table as i on c.contrato_id = i.contrato_id + ) + +-- +select * +from identificadores diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/gold/contratos_resumo.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/gold/contratos_resumo.sql index 5f90eaaa..e76bc716 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos/gold/contratos_resumo.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos/gold/contratos_resumo.sql @@ -1,35 +1,52 @@ -WITH +with -valores_pagos_contratos AS ( - SELECT contrato_id AS id, - SUM(despesas_pagas_movim_liquido) AS despesas_pagas - -- FROM public_silver.silver_contratos_empenhos - FROM {{ ref('contratos_empenhos')}} - WHERE contrato_id IS NOT NULL - GROUP BY contrato_id), - - -contratos_gold AS ( - SELECT *, - CASE - WHEN vp.despesas_pagas = c.valor_global THEN 'Sim' - ELSE 'Não' - END AS pendente_baixa - -- FROM public_raw.contratos c - FROM {{ ref('contratos')}} - LEFT JOIN valores_pagos_contratos vp USING(id)) -- + valores_pagos_contratos as ( + select contrato_id as id, sum(despesas_pagas_movim_liquido) as despesas_pagas + from {{ ref("contratos_empenhos") }} + where contrato_id is not null + group by contrato_id + ), -SELECT - id AS contrato_id, - numero AS numero, - modalidade AS modalidade, - situacao AS situacao, - pendente_baixa AS pendente_baixa, - CONCAT(contratante_orgao_origem_unidade_gestora_origem_codigo, ' - ', contratante_orgao_origem_unidade_gestora_origem_nome_resumido) AS "Unidade", - fornecedor_nome AS fornecedor_nome, - objeto AS objeto, - valor_global AS valor_global, - despesas_pagas AS despesas_pagas, - vigencia_inicio AS vigencia_inicio, - vigencia_fim AS vigencia_fim -FROM contratos_gold \ No newline at end of file + contratos_gold as ( + select + *, + case + when vp.despesas_pagas = c.valor_global then 'Sim' else 'Não' + end as pendente_baixa + from {{ ref("contratos") }} as c + left join valores_pagos_contratos as vp on c.id = vp.id + ) + +-- +select + id as contrato_id, + fornecedor_cnpj_cpf_idgener as fornecedor_cnpj_cpf, + numero, + categoria, + modalidade, + tipo, + situacao, + pendente_baixa, + fornecedor_nome, + objeto, + valor_global, + despesas_pagas, + vigencia_inicio, + vigencia_fim, + num_parcelas, + case + when fornecedor_tipo = 'IDGENERICO' + then 'Empresa do Exterior' + else fornecedor_tipo + end as fornecedor_tipo, + concat( + contratante_orgao_origem_unidade_gestora_origem_codigo, + ' - ', + contratante_orgao_origem_unidade_gestora_origem_nome_resumido + ) as "Unidade", + case + when vigencia_fim - vigencia_inicio >= 730 and num_parcelas > 1 + then 'Sim' + else 'Não' + end as continuado +from contratos_gold diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/silver/contratos_empenhos.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/silver/contratos_empenhos.sql index 807a22e4..fafe8163 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos/silver/contratos_empenhos.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos/silver/contratos_empenhos.sql @@ -1,156 +1,236 @@ -WITH contratos_unicos AS ( - -- Seleciona apenas os registros onde o fornecedor_cnpj_cpf_idgener é único - SELECT * - FROM public_raw.contratos - WHERE fornecedor_cnpj_cpf_idgener IN - (SELECT fornecedor_cnpj_cpf_idgener - FROM public_raw.contratos - GROUP BY fornecedor_cnpj_cpf_idgener - HAVING COUNT(fornecedor_cnpj_cpf_idgener) = 1) -), - -processos_unicos AS ( - -- Seleciona apenas os registros onde o processo é único e não está em contratos_unicos - SELECT * - FROM public_raw.contratos - WHERE processo IN - (SELECT processo - FROM public_raw.contratos - GROUP BY processo - HAVING COUNT(processo) = 1) -), - -numeros_unicos AS ( - -- Seleciona apenas os registros onde o numero é único e não está em contratos_unicos ou processos_unicos - SELECT * - FROM public_raw.contratos - WHERE numero IN - (SELECT numero - FROM public_raw.contratos - GROUP BY numero - HAVING COUNT(numero) = 1) -), - -faturas_contratos AS ( - -- Seleciona uma única correspondência de cada fatura - SELECT DISTINCT ON (f.contrato_id) - f.numero_empenho, - f.contrato_id, - c.* - FROM public_raw.faturas f - JOIN public_raw.contratos c ON f.contrato_id = c.id -), - -empenhos_contratos AS ( - -- Para cada empenho, seleciona apenas uma correspondência de contrato com prioridade na ordem definida - SELECT - ec.*, - COALESCE(c1.id, c2.id, c3.id, f.contrato_id, c_modalidade.id) AS contrato_id, - COALESCE(c1.receita_despesa, c2.receita_despesa, c3.receita_despesa, f.receita_despesa, c_modalidade.receita_despesa) AS receita_despesa, - COALESCE(c1.numero, c2.numero, c3.numero, f.numero, c_modalidade.numero) AS numero, - COALESCE(c1.contratante_orgao_origem_codigo, c2.contratante_orgao_origem_codigo, c3.contratante_orgao_origem_codigo, f.contratante_orgao_origem_codigo, c_modalidade.contratante_orgao_origem_codigo) AS contratante_orgao_origem_codigo, - COALESCE(c1.contratante_orgao_origem_nome, c2.contratante_orgao_origem_nome, c3.contratante_orgao_origem_nome, f.contratante_orgao_origem_nome, c_modalidade.contratante_orgao_origem_nome) AS contratante_orgao_origem_nome, - COALESCE(c1.contratante_orgao_origem_unidade_gestora_origem_codigo, c2.contratante_orgao_origem_unidade_gestora_origem_codigo, c3.contratante_orgao_origem_unidade_gestora_origem_codigo, f.contratante_orgao_origem_unidade_gestora_origem_codigo, c_modalidade.contratante_orgao_origem_unidade_gestora_origem_codigo) AS contratante_orgao_origem_unidade_gestora_origem_codigo, - COALESCE(c1.contratante_orgao_origem_unidade_gestora_origem_nome_resumido, c2.contratante_orgao_origem_unidade_gestora_origem_nome_resumido, c3.contratante_orgao_origem_unidade_gestora_origem_nome_resumido, f.contratante_orgao_origem_unidade_gestora_origem_nome_resumido, c_modalidade.contratante_orgao_origem_unidade_gestora_origem_nome_resumido) AS contratante_orgao_origem_unidade_gestora_origem_nome_resumido, - COALESCE(c1.contratante_orgao_origem_unidade_gestora_origem_nome, c2.contratante_orgao_origem_unidade_gestora_origem_nome, c3.contratante_orgao_origem_unidade_gestora_origem_nome, f.contratante_orgao_origem_unidade_gestora_origem_nome, c_modalidade.contratante_orgao_origem_unidade_gestora_origem_nome) AS contratante_orgao_origem_unidade_gestora_origem_nome, - COALESCE(c1.contratante_orgao_origem_unidade_gestora_origem_sisg, c2.contratante_orgao_origem_unidade_gestora_origem_sisg, c3.contratante_orgao_origem_unidade_gestora_origem_sisg, f.contratante_orgao_origem_unidade_gestora_origem_sisg, c_modalidade.contratante_orgao_origem_unidade_gestora_origem_sisg) AS contratante_orgao_origem_unidade_gestora_origem_sisg, - COALESCE(c1.contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi, c2.contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi, c3.contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi, f.contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi, c_modalidade.contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi) AS contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi, - COALESCE(c1.contratante_orgao_origem_unidade_gestora_origem_utiliza_antecip, c2.contratante_orgao_origem_unidade_gestora_origem_utiliza_antecip, c3.contratante_orgao_origem_unidade_gestora_origem_utiliza_antecip, f.contratante_orgao_origem_unidade_gestora_origem_utiliza_antecip, c_modalidade.contratante_orgao_origem_unidade_gestora_origem_utiliza_antecip) AS contratante_orgao_origem_unidade_gestora_origem_utiliza_antecip, - COALESCE(c1.contratante_orgao_codigo, c2.contratante_orgao_codigo, c3.contratante_orgao_codigo, f.contratante_orgao_codigo, c_modalidade.contratante_orgao_codigo) AS contratante_orgao_codigo, - COALESCE(c1.contratante_orgao_nome, c2.contratante_orgao_nome, c3.contratante_orgao_nome, f.contratante_orgao_nome, c_modalidade.contratante_orgao_nome) AS contratante_orgao_nome, - COALESCE(c1.contratante_orgao_unidade_gestora_codigo, c2.contratante_orgao_unidade_gestora_codigo, c3.contratante_orgao_unidade_gestora_codigo, f.contratante_orgao_unidade_gestora_codigo, c_modalidade.contratante_orgao_unidade_gestora_codigo) AS contratante_orgao_unidade_gestora_codigo, - COALESCE(c1.contratante_orgao_unidade_gestora_nome_resumido, c2.contratante_orgao_unidade_gestora_nome_resumido, c3.contratante_orgao_unidade_gestora_nome_resumido, f.contratante_orgao_unidade_gestora_nome_resumido, c_modalidade.contratante_orgao_unidade_gestora_nome_resumido) AS contratante_orgao_unidade_gestora_nome_resumido, - COALESCE(c1.contratante_orgao_unidade_gestora_nome, c2.contratante_orgao_unidade_gestora_nome, c3.contratante_orgao_unidade_gestora_nome, f.contratante_orgao_unidade_gestora_nome, c_modalidade.contratante_orgao_unidade_gestora_nome) AS contratante_orgao_unidade_gestora_nome, - COALESCE(c1.contratante_orgao_unidade_gestora_sisg, c2.contratante_orgao_unidade_gestora_sisg, c3.contratante_orgao_unidade_gestora_sisg, f.contratante_orgao_unidade_gestora_sisg, c_modalidade.contratante_orgao_unidade_gestora_sisg) AS contratante_orgao_unidade_gestora_sisg, - COALESCE(c1.contratante_orgao_unidade_gestora_utiliza_siafi, c2.contratante_orgao_unidade_gestora_utiliza_siafi, c3.contratante_orgao_unidade_gestora_utiliza_siafi, f.contratante_orgao_unidade_gestora_utiliza_siafi, c_modalidade.contratante_orgao_unidade_gestora_utiliza_siafi) AS contratante_orgao_unidade_gestora_utiliza_siafi, - COALESCE(c1.contratante_orgao_unidade_gestora_utiliza_antecipagov, c2.contratante_orgao_unidade_gestora_utiliza_antecipagov, c3.contratante_orgao_unidade_gestora_utiliza_antecipagov, f.contratante_orgao_unidade_gestora_utiliza_antecipagov, c_modalidade.contratante_orgao_unidade_gestora_utiliza_antecipagov, f.contratante_orgao_unidade_gestora_utiliza_antecipagov) AS contratante_orgao_unidade_gestora_utiliza_antecipagov, - COALESCE(c1.fornecedor_tipo, c2.fornecedor_tipo, c3.fornecedor_tipo, f.fornecedor_tipo, c_modalidade.fornecedor_tipo) AS fornecedor_tipo, - COALESCE(c1.fornecedor_cnpj_cpf_idgener, c2.fornecedor_cnpj_cpf_idgener, c3.fornecedor_cnpj_cpf_idgener, f.fornecedor_cnpj_cpf_idgener, c_modalidade.fornecedor_cnpj_cpf_idgener) AS fornecedor_cnpj_cpf_idgener, - COALESCE(c1.fornecedor_nome, c2.fornecedor_nome, c3.fornecedor_nome, f.fornecedor_nome, c_modalidade.fornecedor_nome) AS fornecedor_nome, - COALESCE(c1.codigo_tipo, c2.codigo_tipo, c3.codigo_tipo, f.codigo_tipo, c_modalidade.codigo_tipo) AS codigo_tipo, - COALESCE(c1.tipo, c2.tipo, c3.tipo, f.tipo, c_modalidade.tipo) AS tipo, - COALESCE(c1.subtipo, c2.subtipo, c3.subtipo, f.subtipo, c_modalidade.subtipo) AS subtipo, - COALESCE(c1.prorrogavel, c2.prorrogavel, c3.prorrogavel, f.prorrogavel, c_modalidade.prorrogavel) AS prorrogavel, - COALESCE(c1.situacao, c2.situacao, c3.situacao, f.situacao, c_modalidade.situacao) AS situacao, - COALESCE(c1.justificativa_inativo, c2.justificativa_inativo, c3.justificativa_inativo, f.justificativa_inativo, c_modalidade.justificativa_inativo) AS justificativa_inativo, - COALESCE(c1.categoria, c2.categoria, c3.categoria, f.categoria, c_modalidade.categoria) AS categoria, - COALESCE(c1.subcategoria, c2.subcategoria, c3.subcategoria, f.subcategoria, c_modalidade.subcategoria) AS subcategoria, - COALESCE(c1.unidades_requisitantes, c2.unidades_requisitantes, c3.unidades_requisitantes, f.unidades_requisitantes, c_modalidade.unidades_requisitantes) AS unidades_requisitantes, - COALESCE(c1.processo, c2.processo, c3.processo, f.processo, c_modalidade.processo) AS processo, - COALESCE(c1.objeto, c2.objeto, c3.objeto, f.objeto, c_modalidade.objeto) AS objeto, - COALESCE(c1.amparo_legal, c2.amparo_legal, c3.amparo_legal, f.amparo_legal, c_modalidade.amparo_legal) AS amparo_legal, - COALESCE(c1.informacao_complementar, c2.informacao_complementar, c3.informacao_complementar, f.informacao_complementar, c_modalidade.informacao_complementar) AS informacao_complementar, - COALESCE(c1.codigo_modalidade, c2.codigo_modalidade, c3.codigo_modalidade, f.codigo_modalidade, c_modalidade.codigo_modalidade) AS codigo_modalidade, - COALESCE(c1.modalidade, c2.modalidade, c3.modalidade, f.modalidade, c_modalidade.modalidade) AS modalidade, - COALESCE(c1.unidade_compra, c2.unidade_compra, c3.unidade_compra, f.unidade_compra, c_modalidade.unidade_compra) AS unidade_compra, - COALESCE(c1.licitacao_numero, c2.licitacao_numero, c3.licitacao_numero, f.licitacao_numero, c_modalidade.licitacao_numero) AS licitacao_numero, - COALESCE(c1.sistema_origem_licitacao, c2.sistema_origem_licitacao, c3.sistema_origem_licitacao, f.sistema_origem_licitacao, c_modalidade.sistema_origem_licitacao) AS sistema_origem_licitacao, - COALESCE(c1.data_assinatura, c2.data_assinatura, c3.data_assinatura, f.data_assinatura, c_modalidade.data_assinatura) AS data_assinatura, - COALESCE(c1.data_publicacao, c2.data_publicacao, c3.data_publicacao, f.data_publicacao, c_modalidade.data_publicacao) AS data_publicacao, - COALESCE(c1.data_proposta_comercial, c2.data_proposta_comercial, c3.data_proposta_comercial, f.data_proposta_comercial, c_modalidade.data_proposta_comercial) AS data_proposta_comercial, - COALESCE(c1.vigencia_inicio, c2.vigencia_inicio, c3.vigencia_inicio, f.vigencia_inicio, c_modalidade.vigencia_inicio) AS vigencia_inicio, - COALESCE(c1.vigencia_fim, c2.vigencia_fim, c3.vigencia_fim, f.vigencia_fim, c_modalidade.vigencia_fim) AS vigencia_fim, - COALESCE(c1.valor_inicial, c2.valor_inicial, c3.valor_inicial, f.valor_inicial, c_modalidade.valor_inicial) AS valor_inicial, - COALESCE(c1.valor_global, c2.valor_global, c3.valor_global, f.valor_global, c_modalidade.valor_global) AS valor_global, - COALESCE(c1.num_parcelas, c2.num_parcelas, c3.num_parcelas, f.num_parcelas, c_modalidade.num_parcelas) AS num_parcelas, - COALESCE(c1.valor_parcela, c2.valor_parcela, c3.valor_parcela, f.valor_parcela, c_modalidade.valor_parcela) AS valor_parcela, - COALESCE(c1.valor_acumulado, c2.valor_acumulado, c3.valor_acumulado, f.valor_acumulado, c_modalidade.valor_acumulado) AS valor_acumulado, - COALESCE(c1.links_historico, c2.links_historico, c3.links_historico, f.links_historico, c_modalidade.links_historico) AS links_historico, - COALESCE(c1.links_empenhos, c2.links_empenhos, c3.links_empenhos, f.links_empenhos, c_modalidade.links_empenhos) AS links_empenhos, - COALESCE(c1.links_cronograma, c2.links_cronograma, c3.links_cronograma, f.links_cronograma, c_modalidade.links_cronograma) AS links_cronograma, - COALESCE(c1.links_garantias, c2.links_garantias, c3.links_garantias, f.links_garantias, c_modalidade.links_garantias) AS links_garantias, - COALESCE(c1.links_itens, c2.links_itens, c3.links_itens, f.links_itens, c_modalidade.links_itens) AS links_itens, - COALESCE(c1.links_prepostos, c2.links_prepostos, c3.links_prepostos, f.links_prepostos, c_modalidade.links_prepostos) AS links_prepostos, - COALESCE(c1.links_responsaveis, c2.links_responsaveis, c3.links_responsaveis, f.links_responsaveis, c_modalidade.links_responsaveis) AS links_responsaveis, - COALESCE(c1.links_despesas_acessorias, c2.links_despesas_acessorias, c3.links_despesas_acessorias, f.links_despesas_acessorias, c_modalidade.links_despesas_acessorias) AS links_despesas_acessorias, - COALESCE(c1.links_faturas, c2.links_faturas, c3.links_faturas, f.links_faturas, c_modalidade.links_faturas) AS links_faturas, - COALESCE(c1.links_ocorrencias, c2.links_ocorrencias, c3.links_ocorrencias, f.links_ocorrencias, c_modalidade.links_ocorrencias) AS links_ocorrencias, - COALESCE(c1.links_terceirizados, c2.links_terceirizados, c3.links_terceirizados, f.links_terceirizados, c_modalidade.links_terceirizados) AS links_terceirizados, - COALESCE(c1.links_arquivos, c2.links_arquivos, c3.links_arquivos, f.links_arquivos, c_modalidade.links_arquivos) AS links_arquivos, - - CASE - WHEN ec.ne_ccor_favorecido = c1.fornecedor_cnpj_cpf_idgener THEN 'fornecedor_cnpj_cpf_idgener' - WHEN ec.ne_ccor_favorecido != c1.fornecedor_cnpj_cpf_idgener - AND REGEXP_REPLACE(ec.ne_num_processo, '[\./-]', '', 'g') = c2.processo THEN 'processo' - WHEN ec.ne_ccor_favorecido != c1.fornecedor_cnpj_cpf_idgener - AND REGEXP_REPLACE(ec.ne_num_processo, '[\./-]', '', 'g') != c2.processo - AND RIGHT(ec.ne_ccor, 12) = c3.numero THEN 'numero' - WHEN ec.ne_ccor_favorecido != c1.fornecedor_cnpj_cpf_idgener - AND REGEXP_REPLACE(ec.ne_num_processo, '[\./-]', '', 'g') != c2.processo - AND RIGHT(ec.ne_ccor, 12) != c3.numero - AND RIGHT(ec.ne_ccor, 12) = f.numero_empenho THEN 'faturas' - WHEN ec.ne_ccor_favorecido != c1.fornecedor_cnpj_cpf_idgener - AND REGEXP_REPLACE(ec.ne_num_processo, '[\./-]', '', 'g') != c2.processo - AND RIGHT(ec.ne_ccor, 12) != c3.numero - AND RIGHT(ec.ne_ccor, 12) != f.numero_empenho - AND ( - (c_modalidade.codigo_modalidade = 5 - AND ec.ne_informacao_complementar LIKE CONCAT('%', c_modalidade.contratante_orgao_unidade_gestora_codigo, TO_CHAR(c_modalidade.codigo_modalidade, 'FM00'), REGEXP_REPLACE(c_modalidade.numero, '[\./-]', '', 'g'), '%')) - OR - (c_modalidade.codigo_modalidade = 7 - AND ec.ne_informacao_complementar LIKE CONCAT('%', c_modalidade.contratante_orgao_unidade_gestora_codigo, TO_CHAR(c_modalidade.codigo_modalidade, 'FM00'), REGEXP_REPLACE(c_modalidade.licitacao_numero, '[\./-]', '', 'g'), '%')) - ) - THEN 'informacao_complementar' - ELSE NULL - END AS origem - - FROM {{ ref('empenhos_tesouro') }} ec - LEFT JOIN contratos_unicos c1 ON ec.ne_ccor_favorecido = c1.fornecedor_cnpj_cpf_idgener - LEFT JOIN processos_unicos c2 ON REGEXP_REPLACE(ec.ne_num_processo, '[\./-]', '', 'g') = c2.processo AND c1.id IS NULL - LEFT JOIN numeros_unicos c3 ON RIGHT(ec.ne_ccor, 12) = c3.numero AND c1.id IS NULL AND c2.id IS NULL - LEFT JOIN faturas_contratos f ON RIGHT(ec.ne_ccor,12) = f.numero_empenho AND c1.id IS NULL AND c2.id IS NULL AND c3.id IS NULL - LEFT JOIN public_raw.contratos c_modalidade ON - ( - ( - c_modalidade.codigo_modalidade = '5' - AND ec.ne_informacao_complementar like CONCAT('%',c_modalidade.contratante_orgao_unidade_gestora_codigo, TO_CHAR(c_modalidade.codigo_modalidade, 'FM00'), REGEXP_REPLACE(c_modalidade.numero, '[\./-]', '', 'g'), '%') +with + contratos_com_ne_cnpj_cpf as ( + select distinct contrato_id, ne, cnpj_cpf + from {{ ref("identificadores") }} + where ne is not null + ), + + empenhos_tesouro_transformed as ( + select + *, + case + when ne_ccor is not null then upper(right(ne_ccor, 12)) + end as ne_transformed + from {{ ref("empenhos_tesouro") }} + ), + + -- Primeiro merge: apenas os contratos que tem ne e cnpj_cpf + full_join as ( + select + *, + case + when c.cnpj_cpf is not null and e.ne_ccor_favorecido is not null + then 'both' + when c.cnpj_cpf is not null and e.ne_ccor_favorecido is null + then 'left only' -- nao existe no empenhos_tesouro + when c.cnpj_cpf is null and e.ne_ccor_favorecido is not null + then 'right only' -- existe no empenhos_tesouro mas nao no raw.contratos (ESSA QUE NÓS QUEREMOS) + end as origem + from contratos_com_ne_cnpj_cpf as c + full join + empenhos_tesouro_transformed as e + on c.ne = e.ne_transformed + and c.cnpj_cpf = e.ne_ccor_favorecido + ), + + resultado_1 as ( + select + contrato_id, + coalesce(ne_transformed, ne) as ne_transformed, + ne_ccor, + ne_informacao_complementar, + ne_num_processo, + ne_ccor_descricao, + doc_observacao, + natureza_despesa, + natureza_despesa_1, + natureza_despesa_detalhada, + natureza_despesa_detalhada_1, + ne_ccor_favorecido, + ne_ccor_favorecido_1, + ne_ccor_ano_emissao, + ne_ccor_ano_emissao_1, + despesas_empenhadas_saldo, + despesas_empenhadas_movim_liquido, + despesas_liquidadas_saldo, + despesas_liquidadas_movim_liquido, + despesas_pagas_saldo, + despesas_pagas_movim_liquido + from full_join + where origem = 'both' or origem = 'left only' + -- contrato_id nulo significa lacuna no lado esquerdo do RIGHT JOIN, + -- ou contratos em que o join usando ne e cnpj/cpf não foi possível + ), + + -- --------------------------------------------------------------------------------------------------- + empenhos_restantes_1 as ( + select * + from empenhos_tesouro_transformed + where ne_ccor in (select ne_ccor from full_join where origem = 'right only') + ), + + todos_contratos as ( + select distinct contrato_id, processo, cnpj_cpf, info_complementar + from {{ ref("identificadores") }} + ), + + -- Segundo merge: usando processos em que há um único contrato + processos_unicos as ( + select distinct contrato_id, processo as num_processo + from todos_contratos + where + processo in ( + select processo from todos_contratos group by processo having count(*) = 1 ) - OR - ( - c_modalidade.codigo_modalidade = '7' - AND ec.ne_informacao_complementar like CONCAT('%',c_modalidade.contratante_orgao_unidade_gestora_codigo, TO_CHAR(c_modalidade.codigo_modalidade, 'FM00'), REGEXP_REPLACE(c_modalidade.licitacao_numero, '[\./-]', '', 'g'), '%') - ) - ) AND c1.id IS NULL AND c2.id IS NULL AND c3.id IS NULL AND f.id IS NULL -) - -SELECT * FROM empenhos_contratos \ No newline at end of file + ), + + juncao_processo as ( + select * + from processos_unicos as cpu + right join empenhos_restantes_1 as er on cpu.num_processo = er.ne_num_processo + ), + + resultado_2 as ( + select + contrato_id, + ne_transformed, + ne_ccor, + ne_informacao_complementar, + ne_num_processo, + ne_ccor_descricao, + doc_observacao, + natureza_despesa, + natureza_despesa_1, + natureza_despesa_detalhada, + natureza_despesa_detalhada_1, + ne_ccor_favorecido, + ne_ccor_favorecido_1, + ne_ccor_ano_emissao, + ne_ccor_ano_emissao_1, + despesas_empenhadas_saldo, + despesas_empenhadas_movim_liquido, + despesas_liquidadas_saldo, + despesas_liquidadas_movim_liquido, + despesas_pagas_saldo, + despesas_pagas_movim_liquido + from juncao_processo + -- WHERE origem = 'both' + where contrato_id is not null + ), + + -- --------------------------------------------------------------------------------------------------- + empenhos_restantes_2 as ( + select * + from empenhos_restantes_1 + where ne_ccor not in (select ne_ccor from resultado_2) + ), + + -- Terceiro merge: usando CNPJs que possuem um único contrato + cnpjs_unicos as ( + select distinct contrato_id, cnpj_cpf + from todos_contratos + where + cnpj_cpf in ( + select cnpj_cpf from todos_contratos group by cnpj_cpf having count(*) = 1 + ) + ), + + juncao_cnpjs as ( + select * + from cnpjs_unicos as cu + right join empenhos_restantes_2 as er on cu.cnpj_cpf = er.ne_ccor_favorecido + ), + + resultado_3 as ( + select + contrato_id, + ne_transformed, + ne_ccor, + ne_informacao_complementar, + ne_num_processo, + ne_ccor_descricao, + doc_observacao, + natureza_despesa, + natureza_despesa_1, + natureza_despesa_detalhada, + natureza_despesa_detalhada_1, + ne_ccor_favorecido, + ne_ccor_favorecido_1, + ne_ccor_ano_emissao, + ne_ccor_ano_emissao_1, + despesas_empenhadas_saldo, + despesas_empenhadas_movim_liquido, + despesas_liquidadas_saldo, + despesas_liquidadas_movim_liquido, + despesas_pagas_saldo, + despesas_pagas_movim_liquido + from juncao_cnpjs + where contrato_id is not null + ), + + -- --------------------------------------------------------------------------------------------------- + empenhos_restantes_3 as ( + select + *, + -- garantir que ambos os lados estão no mesmo formato + substring(ne_informacao_complementar from '^([0-9]+) -') as info_complementar + from empenhos_restantes_2 + where ne_ccor not in (select ne_ccor from resultado_3) + ), + + contratos_info_complementar as ( + select distinct contrato_id, info_complementar from todos_contratos + ), + + -- Quarto merge: usando "informação complementar", que é um agregado da unidade + -- gestora + modalidade + numero do contrato ou numero da licitação + juncao_info_complementar as ( + select * + from contratos_info_complementar as c + right join empenhos_restantes_3 as e on c.info_complementar = e.info_complementar + ), + + resultado_4 as ( + select + contrato_id, + ne_transformed, + ne_ccor, + ne_informacao_complementar, + ne_num_processo, + ne_ccor_descricao, + doc_observacao, + natureza_despesa, + natureza_despesa_1, + natureza_despesa_detalhada, + natureza_despesa_detalhada_1, + ne_ccor_favorecido, + ne_ccor_favorecido_1, + ne_ccor_ano_emissao, + ne_ccor_ano_emissao_1, + despesas_empenhadas_saldo, + despesas_empenhadas_movim_liquido, + despesas_liquidadas_saldo, + despesas_liquidadas_movim_liquido, + despesas_pagas_saldo, + despesas_pagas_movim_liquido + from juncao_info_complementar + where contrato_id is not null + ), + + -- União de todos os resultados parciais + resultado_final as ( + select * + from resultado_1 + union + select * + from resultado_2 + union + select * + from resultado_3 + union + select * + from resultado_4 + ) + +select * +from resultado_final diff --git a/airflow_lappis/dags/dbt/ipea/models/sources.yml b/airflow_lappis/dags/dbt/ipea/models/sources.yml new file mode 100644 index 00000000..a32555c1 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sources.yml @@ -0,0 +1,17 @@ +version: 2 + +sources: + - name: compras_gov + schema: raw + tables: + - name: contratos + - name: faturas + - name: empenhos + - name: cronogramas + + - name: siafi + schema: raw + tables: + - name: empenhos_tesouro + - name: estagios + diff --git a/airflow_lappis/dags/dbt/ipea/profiles.yml b/airflow_lappis/dags/dbt/ipea/profiles.yml index 5aceadf1..90616362 100644 --- a/airflow_lappis/dags/dbt/ipea/profiles.yml +++ b/airflow_lappis/dags/dbt/ipea/profiles.yml @@ -9,3 +9,4 @@ ipea: port: 5432 dbname: analytics schema: ipea + diff --git a/airflow_lappis/dags/dbt/ipea/snapshots/contratos_snapshot.yml b/airflow_lappis/dags/dbt/ipea/snapshots/contratos_snapshot.yml new file mode 100644 index 00000000..30ef5d79 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/snapshots/contratos_snapshot.yml @@ -0,0 +1,10 @@ +snapshots: + - name: contratos_snapshot + relation: ref('contratos') + config: + schema: snapshots + database: analytics + unique_key: id + strategy: timestamp + updated_at: updated_at + dbt_valid_to_current: "to_date('9999-12-31', 'YYYY-MM-DD')" \ No newline at end of file diff --git a/dbt/__init__.py b/dbt/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/dbt_project.yml b/dbt_project.yml deleted file mode 100644 index c957d047..00000000 --- a/dbt_project.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: 'airflow_dbt_project' -version: '1.0.0' - -profile: 'airflow_dbt_project' - -model-paths: ["models"] -analysis-paths: ["analyses"] -test-paths: ["tests"] -seed-paths: ["seeds"] -macro-paths: ["macros"] -snapshot-paths: ["snapshots"] - -clean-targets: - - "target" - - "dbt_packages" - - -models: - airflow_dbt_project: - silver: - +schema: silver - +materialized: table - gold: - +schema: gold - +materialized: table diff --git a/profiles.yml b/profiles.yml deleted file mode 100644 index 23dea3f0..00000000 --- a/profiles.yml +++ /dev/null @@ -1,11 +0,0 @@ -default: - outputs: - dev: - type: postgres - host: 10.0.0.73 - user: analytics - password: xQ3hxNJThsVx3WqEp6Yr0hhtptSMbmbFWyL2 - port: 5432 - dbname: analytics - schema: public - target: dev From dc56cc67d08a623d773f76414fed9d34d5cda87e Mon Sep 17 00:00:00 2001 From: Matheus Dias Maciel Date: Sun, 16 Feb 2025 13:28:42 +0000 Subject: [PATCH 013/317] Adding Cosmos dag --- .sqlfluffignore | 2 + Dockerfile | 12 +++-- Makefile | 11 +++-- airflow_lappis/__init__.py | 0 airflow_lappis/airflow.cfg | 6 +++ airflow_lappis/dags/__init__.py | 0 airflow_lappis/dags/data_ingest/__init__.py | 0 .../contratos_inativos_ingest_dag.py | 0 .../dags/data_ingest/contratos_ingest_dag.py | 0 .../dags/data_ingest/cronograma_ingest_dag.py | 0 .../dags/data_ingest/empenhos_ingest_dag.py | 0 .../empenhos_tesouro_ingest_dag.py | 0 .../estagios_tesouro_ingest_dag.py | 0 .../dags/data_ingest/faturas_ingest_dag.py | 0 .../unidade_organizacional_ingest_dag.py | 0 airflow_lappis/dags/dbt/.user.yml | 0 airflow_lappis/dags/dbt/ipea/.user.yml | 0 airflow_lappis/dags/dbt/ipea/cosmos_dag.py | 22 +++++++++ airflow_lappis/dags/dbt/ipea/dbt_project.yml | 0 .../dbt/ipea/macros/get_custom_schema.sql | 0 .../models/contratos/bronze/contratos.sql | 44 +++++++++--------- .../ipea/models/contratos/bronze/empenhos.sql | 0 .../contratos/bronze/empenhos_tesouro.sql | 2 +- .../ipea/models/contratos/bronze/estagios.sql | 41 +++++++---------- .../ipea/models/contratos/bronze/faturas.sql | 0 .../contratos/gold/contratos_resumo.sql | 0 .../contratos/silver/contratos_empenhos.sql | 0 .../dags/dbt/ipea/models/sources.yml | 4 +- airflow_lappis/dags/dbt/ipea/profiles.yml | 0 airflow_lappis/helpers/__init__.py | 0 airflow_lappis/helpers/postgres_helpers.py | 0 airflow_lappis/plugins/__init__.py | 0 airflow_lappis/plugins/cliente_base.py | 0 airflow_lappis/plugins/cliente_contratos.py | 0 airflow_lappis/plugins/cliente_email.py | 0 airflow_lappis/plugins/cliente_estrutura.py | 0 airflow_lappis/plugins/cliente_postgres.py | 0 airflow_lappis/plugins/cliente_siafi.py | 0 airflow_lappis/plugins/cliente_siape.py | 0 docker-compose.yml | 45 ++++++++++--------- jupyter/__init__.py | 0 pyproject.toml | 43 +++++++++++++++--- requirements.txt | 32 +++++++------ 43 files changed, 162 insertions(+), 102 deletions(-) create mode 100644 .sqlfluffignore delete mode 100644 airflow_lappis/__init__.py create mode 100644 airflow_lappis/airflow.cfg delete mode 100644 airflow_lappis/dags/__init__.py delete mode 100644 airflow_lappis/dags/data_ingest/__init__.py mode change 100644 => 100755 airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py mode change 100644 => 100755 airflow_lappis/dags/data_ingest/contratos_ingest_dag.py mode change 100644 => 100755 airflow_lappis/dags/data_ingest/cronograma_ingest_dag.py mode change 100644 => 100755 airflow_lappis/dags/data_ingest/empenhos_ingest_dag.py mode change 100644 => 100755 airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py mode change 100644 => 100755 airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py mode change 100644 => 100755 airflow_lappis/dags/data_ingest/faturas_ingest_dag.py mode change 100644 => 100755 airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py mode change 100644 => 100755 airflow_lappis/dags/dbt/.user.yml mode change 100644 => 100755 airflow_lappis/dags/dbt/ipea/.user.yml create mode 100755 airflow_lappis/dags/dbt/ipea/cosmos_dag.py mode change 100644 => 100755 airflow_lappis/dags/dbt/ipea/dbt_project.yml mode change 100644 => 100755 airflow_lappis/dags/dbt/ipea/macros/get_custom_schema.sql mode change 100644 => 100755 airflow_lappis/dags/dbt/ipea/models/contratos/bronze/contratos.sql mode change 100644 => 100755 airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos.sql mode change 100644 => 100755 airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql mode change 100644 => 100755 airflow_lappis/dags/dbt/ipea/models/contratos/bronze/estagios.sql mode change 100644 => 100755 airflow_lappis/dags/dbt/ipea/models/contratos/bronze/faturas.sql mode change 100644 => 100755 airflow_lappis/dags/dbt/ipea/models/contratos/gold/contratos_resumo.sql mode change 100644 => 100755 airflow_lappis/dags/dbt/ipea/models/contratos/silver/contratos_empenhos.sql mode change 100644 => 100755 airflow_lappis/dags/dbt/ipea/profiles.yml delete mode 100644 airflow_lappis/helpers/__init__.py mode change 100644 => 100755 airflow_lappis/helpers/postgres_helpers.py delete mode 100644 airflow_lappis/plugins/__init__.py mode change 100644 => 100755 airflow_lappis/plugins/cliente_base.py mode change 100644 => 100755 airflow_lappis/plugins/cliente_contratos.py mode change 100644 => 100755 airflow_lappis/plugins/cliente_email.py mode change 100644 => 100755 airflow_lappis/plugins/cliente_estrutura.py mode change 100644 => 100755 airflow_lappis/plugins/cliente_postgres.py mode change 100644 => 100755 airflow_lappis/plugins/cliente_siafi.py mode change 100644 => 100755 airflow_lappis/plugins/cliente_siape.py delete mode 100644 jupyter/__init__.py diff --git a/.sqlfluffignore b/.sqlfluffignore new file mode 100644 index 00000000..3c8cce41 --- /dev/null +++ b/.sqlfluffignore @@ -0,0 +1,2 @@ +**/target/**/*.sql +**/compiled/**/*.sql diff --git a/Dockerfile b/Dockerfile index a1d8b92f..7c38ad83 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,13 +30,11 @@ RUN apt-get update \ && sed -i 's/^# pt_BR.UTF-8 UTF-8$/pt_BR.UTF-8 UTF-8/g' /etc/locale.gen \ && locale-gen en_US.UTF-8 pt_BR.UTF-8 \ && update-locale LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 + USER airflow +WORKDIR ${AIRFLOW_HOME} # Para rodar o airflow só precisamos instalar as dependências visto que o código -# sempre será sincronizado via git sync ou via volumes localmente -RUN pip install --no-cache-dir poetry -WORKDIR /opt -COPY poetry.lock pyproject.toml ./ -RUN poetry install --no-root - -WORKDIR ${AIRFLOW_HOME} +# sempre será sincronizado via git sync ou via volumes localmente +COPY requirements.txt . +RUN pip install -r requirements.txt diff --git a/Makefile b/Makefile index 1d5478fa..58b5b6a6 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,6 @@ +export PYTHONPATH := $(CURDIR)/airflow_lappis +export MYPYPATH := $(CURDIR):$(CURDIR)/airflow_lappis/dags:$(CURDIR)/airflow_lappis/helpers:$(CURDIR)/airflow_lappis/plugins + setup: pip install poetry==1.8.5 poetry config virtualenvs.in-project false @@ -10,14 +13,14 @@ setup: format: poetry run black . poetry run ruff check --fix . - poetry run sqlfmt ./dbt - poetry run sqlfluff fix ./dbt + poetry run sqlfmt ./airflow_lappis/dags/dbt lint: poetry run black . --check poetry run ruff check . - poetry run mypy . --explicit-package-bases --exclude 'airflow_lappis/helpers/__init__.py|airflow_lappis/plugins/__init__.py' - poetry run sqlfmt ./airflow_lappis/dags/dbt/ipea --check + poetry run mypy . --explicit-package-bases --install-types + poetry run sqlfmt ./airflow_lappis/dags/dbt --check + [ "${GITLAB_CI}" ] || poetry run sqlfluff lint ./airflow_lappis/dags/dbt test: poetry run pytest tests diff --git a/airflow_lappis/__init__.py b/airflow_lappis/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/airflow_lappis/airflow.cfg b/airflow_lappis/airflow.cfg new file mode 100644 index 00000000..081853c1 --- /dev/null +++ b/airflow_lappis/airflow.cfg @@ -0,0 +1,6 @@ +[core] +dags_folder = /opt/airflow/dags +plugins_folder = /opt/airflow/plugins + +[logging] +extra_path = /opt/airflow/helpers diff --git a/airflow_lappis/dags/__init__.py b/airflow_lappis/dags/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/airflow_lappis/dags/data_ingest/__init__.py b/airflow_lappis/dags/data_ingest/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py b/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py old mode 100644 new mode 100755 diff --git a/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py b/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py old mode 100644 new mode 100755 diff --git a/airflow_lappis/dags/data_ingest/cronograma_ingest_dag.py b/airflow_lappis/dags/data_ingest/cronograma_ingest_dag.py old mode 100644 new mode 100755 diff --git a/airflow_lappis/dags/data_ingest/empenhos_ingest_dag.py b/airflow_lappis/dags/data_ingest/empenhos_ingest_dag.py old mode 100644 new mode 100755 diff --git a/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py old mode 100644 new mode 100755 diff --git a/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py old mode 100644 new mode 100755 diff --git a/airflow_lappis/dags/data_ingest/faturas_ingest_dag.py b/airflow_lappis/dags/data_ingest/faturas_ingest_dag.py old mode 100644 new mode 100755 diff --git a/airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py b/airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py old mode 100644 new mode 100755 diff --git a/airflow_lappis/dags/dbt/.user.yml b/airflow_lappis/dags/dbt/.user.yml old mode 100644 new mode 100755 diff --git a/airflow_lappis/dags/dbt/ipea/.user.yml b/airflow_lappis/dags/dbt/ipea/.user.yml old mode 100644 new mode 100755 diff --git a/airflow_lappis/dags/dbt/ipea/cosmos_dag.py b/airflow_lappis/dags/dbt/ipea/cosmos_dag.py new file mode 100755 index 00000000..c335e582 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/cosmos_dag.py @@ -0,0 +1,22 @@ +import os +from datetime import datetime +from cosmos import DbtDag, ProjectConfig, ProfileConfig, ExecutionConfig + +profile_config = ProfileConfig( + profiles_yml_filepath=f"{os.environ['AIRFLOW_HOME']}/dags/dbt/ipea/profiles.yml", + profile_name="ipea", + target_name="prod", +) + +my_cosmos_dag = DbtDag( + project_config=ProjectConfig(f"{os.environ['AIRFLOW_HOME']}/dags/dbt/ipea"), + profile_config=profile_config, + execution_config=ExecutionConfig( + dbt_executable_path=f"{os.environ['AIRFLOW_HOME']}/.local/bin/dbt", + ), + schedule_interval="@daily", + start_date=datetime(2025, 1, 1), + catchup=False, + dag_id="ipea_cosmos_dag", + default_args={"retries": 2}, +) diff --git a/airflow_lappis/dags/dbt/ipea/dbt_project.yml b/airflow_lappis/dags/dbt/ipea/dbt_project.yml old mode 100644 new mode 100755 diff --git a/airflow_lappis/dags/dbt/ipea/macros/get_custom_schema.sql b/airflow_lappis/dags/dbt/ipea/macros/get_custom_schema.sql old mode 100644 new mode 100755 diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/contratos.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/contratos.sql old mode 100644 new mode 100755 index f23c1851..a22bb83f --- a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/contratos.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/contratos.sql @@ -49,11 +49,32 @@ with licitacao_numero, sistema_origem_licitacao, cast(num_parcelas as int) as num_parcelas, + cast( + replace( + replace(cast(valor_inicial as text), '.', ''), ',', '.' + ) as numeric(15, 2) + ) as valor_inicial, + -- Tratar valores nulos ou inválidos nas colunas de data + cast( + replace( + replace(cast(valor_global as text), '.', ''), ',', '.' + ) as numeric(15, 2) + ) as valor_global, + cast( + replace( + replace(cast(valor_parcela as text), '.', ''), ',', '.' + ) as numeric(15, 2) + ) as valor_parcela, + cast( + replace( + replace(cast(valor_acumulado as text), '.', ''), ',', '.' + ) as numeric(15, 2) + ) as valor_acumulado, regexp_replace( fornecedor_cnpj_cpf_idgener, '[^0-9A-Za-z]', '', 'g' ) as fornecedor_cnpj_cpf_idgener, - -- Tratar valores nulos ou inválidos nas colunas de data regexp_replace(processo, '[^0-9A-Za-z]', '', 'g') as processo, + -- Conversão de valores numéricos para FLOAT ou INT case when data_assinatura is null then null @@ -91,7 +112,6 @@ with -- Retorna NULL se não for uma data válida then to_date(cast(vigencia_inicio as text), 'YYYY-MM-DD') end as vigencia_inicio, - -- Conversão de valores numéricos para FLOAT ou INT case when vigencia_fim is null then null @@ -101,26 +121,6 @@ with -- Retorna NULL se não for uma data válida then to_date(cast(vigencia_fim as text), 'YYYY-MM-DD') end as vigencia_fim, - cast( - replace( - replace(cast(valor_inicial as text), '.', ''), ',', '.' - ) as numeric(15, 2) - ) as valor_inicial, - cast( - replace( - replace(cast(valor_global as text), '.', ''), ',', '.' - ) as numeric(15, 2) - ) as valor_global, - cast( - replace( - replace(cast(valor_parcela as text), '.', ''), ',', '.' - ) as numeric(15, 2) - ) as valor_parcela, - cast( - replace( - replace(cast(valor_acumulado as text), '.', ''), ',', '.' - ) as numeric(15, 2) - ) as valor_acumulado, now() as updated_at from {{ source("compras_gov", "contratos") }} ) -- diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos.sql old mode 100644 new mode 100755 diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql old mode 100644 new mode 100755 index 3dea10af..d645f5fd --- a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql @@ -5,7 +5,6 @@ with id::integer as id, ne_ccor::text as ne_ccor, ne_informacao_complementar::text as ne_informacao_complementar, - regexp_replace(ne_num_processo, '[./-]', '') as ne_num_processo, ne_ccor_descricao::text as ne_ccor_descricao, doc_observacao::text as doc_observacao, natureza_despesa::integer as natureza_despesa, @@ -16,6 +15,7 @@ with ne_ccor_favorecido_1::text as ne_ccor_favorecido_1, ne_ccor_ano_emissao::integer as ne_ccor_ano_emissao, item_informacao::integer as ne_ccor_ano_emissao_1, + regexp_replace(ne_num_processo, '[./-]', '') as ne_num_processo, -- Aplicando NULLIF e removendo parênteses antes de converter para NUMERIC {{ target.schema }}.parse_number( despesas_empenhadas_controle_empenho_saldo_moeda_origem diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/estagios.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/estagios.sql old mode 100644 new mode 100755 index 3e31bc19..25512a60 --- a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/estagios.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/estagios.sql @@ -17,48 +17,41 @@ with ne_informacao_complementar::text, -- Remove o "0" inicial e pontos, barras e hífens do número do processo + ne_ccor_descricao::text, + + doc_observacao::text, + natureza_despesa_1, + + natureza_despesa_detalhada_1, + + ne_ccor_favorecido, + + ne_ccor_favorecido_1, + + ne_ccor_mes_emissao, + mes_lancamento, case when length(ne_num_processo::text) > 3 then regexp_replace(ltrim(ne_num_processo::text, '0'), '[\./-]', '', 'g') else ne_num_processo::text end as ne_num_processo, - ne_ccor_descricao::text, - doc_observacao::text, - case - when natureza_despesa::text ~ '^\d+$' - then cast(natureza_despesa as integer) - else null + when natureza_despesa::text ~ '^\d+$' then natureza_despesa::integer end as natureza_despesa, - natureza_despesa_1, - case when natureza_despesa_detalhada::text ~ '^\d+$' - then cast(natureza_despesa_detalhada as integer) - else null + then natureza_despesa_detalhada::integer end as natureza_despesa_detalhada, - natureza_despesa_detalhada_1, - ne_ccor_favorecido, - ne_ccor_favorecido_1, - case - when ano_lancamento::text ~ '^\d+$' - then cast(ano_lancamento as integer) - else null + when ano_lancamento::text ~ '^\d+$' then ano_lancamento::integer end as ano_lancamento, - ne_ccor_mes_emissao, - case - when ne_ccor_ano_emissao::text ~ '^\d+$' - then cast(ne_ccor_ano_emissao as integer) - else null + when ne_ccor_ano_emissao::text ~ '^\d+$' then ne_ccor_ano_emissao::integer end as ne_ccor_ano_emissao, - - mes_lancamento, {{ target.schema }}.parse_number( despesas_empenhadas_controle_empenho_saldo_moeda_origem ) as despesas_empenhadas_controle_empenho_saldo_moeda_origem, diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/faturas.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/faturas.sql old mode 100644 new mode 100755 diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/gold/contratos_resumo.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/gold/contratos_resumo.sql old mode 100644 new mode 100755 diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/silver/contratos_empenhos.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/silver/contratos_empenhos.sql old mode 100644 new mode 100755 diff --git a/airflow_lappis/dags/dbt/ipea/models/sources.yml b/airflow_lappis/dags/dbt/ipea/models/sources.yml index a32555c1..d5eac843 100644 --- a/airflow_lappis/dags/dbt/ipea/models/sources.yml +++ b/airflow_lappis/dags/dbt/ipea/models/sources.yml @@ -2,7 +2,7 @@ version: 2 sources: - name: compras_gov - schema: raw + schema: compras_gov tables: - name: contratos - name: faturas @@ -10,7 +10,7 @@ sources: - name: cronogramas - name: siafi - schema: raw + schema: siafi tables: - name: empenhos_tesouro - name: estagios diff --git a/airflow_lappis/dags/dbt/ipea/profiles.yml b/airflow_lappis/dags/dbt/ipea/profiles.yml old mode 100644 new mode 100755 diff --git a/airflow_lappis/helpers/__init__.py b/airflow_lappis/helpers/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/airflow_lappis/helpers/postgres_helpers.py b/airflow_lappis/helpers/postgres_helpers.py old mode 100644 new mode 100755 diff --git a/airflow_lappis/plugins/__init__.py b/airflow_lappis/plugins/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/airflow_lappis/plugins/cliente_base.py b/airflow_lappis/plugins/cliente_base.py old mode 100644 new mode 100755 diff --git a/airflow_lappis/plugins/cliente_contratos.py b/airflow_lappis/plugins/cliente_contratos.py old mode 100644 new mode 100755 diff --git a/airflow_lappis/plugins/cliente_email.py b/airflow_lappis/plugins/cliente_email.py old mode 100644 new mode 100755 diff --git a/airflow_lappis/plugins/cliente_estrutura.py b/airflow_lappis/plugins/cliente_estrutura.py old mode 100644 new mode 100755 diff --git a/airflow_lappis/plugins/cliente_postgres.py b/airflow_lappis/plugins/cliente_postgres.py old mode 100644 new mode 100755 diff --git a/airflow_lappis/plugins/cliente_siafi.py b/airflow_lappis/plugins/cliente_siafi.py old mode 100644 new mode 100755 diff --git a/airflow_lappis/plugins/cliente_siape.py b/airflow_lappis/plugins/cliente_siape.py old mode 100644 new mode 100755 diff --git a/docker-compose.yml b/docker-compose.yml index 50ddf94d..10dad517 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3.8' - # Base configuration for Airflow services x-airflow-common: &airflow-common build: . @@ -7,9 +5,11 @@ x-airflow-common: &airflow-common env_file: - .env volumes: + - ./airflow_lappis/airflow.cfg:${AIRFLOW_HOME}/airflow.cfg - ./airflow_lappis/dags:${AIRFLOW_HOME}/dags/ - ./airflow_lappis/plugins:${AIRFLOW_HOME}/plugins/ - ./airflow_lappis/helpers:${AIRFLOW_HOME}/helpers/ + - ./airflow_lappis/dags/dbt/ipea/profiles.yml:${AIRFLOW_HOME}/.dbt/profiles.yml depends_on: &airflow-common-depends-on postgres: condition: service_healthy @@ -31,7 +31,7 @@ x-airflow-environment: &airflow-common-env AIRFLOW__WEBSERVER__NAVBAR_COLOR: '#98DFFF' AIRFLOW__WEBSERVER__RELOAD_ON_PLUGIN_CHANGE: 'true' AIRFLOW__WEBSERVER__SECRET_KEY: '42' - PYTHONPATH: '${AIRFLOW_HOME}/dags:${AIRFLOW_HOME}/plugins:${AIRFLOW_HOME}/helpers' + PYTHONPATH: "/opt/airflow/dags:/opt/airflow/plugins:/opt/airflow/helpers:$PYTHONPATH" _AIRFLOW_DB_MIGRATE: 'true' _AIRFLOW_WWW_USER_CREATE: 'true' _AIRFLOW_WWW_USER_USERNAME: ${_AIRFLOW_WWW_USER_USERNAME:-airflow} @@ -40,6 +40,26 @@ x-airflow-environment: &airflow-common-env AIRFLOW__CORE__DAGS_FOLDER: ${AIRFLOW_HOME}/dags services: + # Airflow Services + airflow: + <<: *airflow-common + # Usamos o comando de standalone aqui para não rodar múltiplos containers + # do airflow localmente e assim melhorar a velocidade inicialização + # doc do standalone: https://airflow.apache.org/docs/apache-airflow/2.8.1/start.html + command: standalone + ports: + - "8080:8080" + healthcheck: + test: [ "CMD", "curl", "--fail", "http://localhost:8080/health" ] + interval: 10s + timeout: 60s + start_period: 60s + retries: 5 + restart: always + environment: + <<: *airflow-common-env + + # Postgres database postgres: image: postgres:15-alpine env_file: @@ -60,25 +80,6 @@ services: retries: 5 restart: always - # Airflow Services - airflow: - <<: *airflow-common - # Usamos o comando de standalone aqui para não rodar múltiplos containers - # do airflow localmente e assim melhorar a velocidade inicialização - # doc do standalone: https://airflow.apache.org/docs/apache-airflow/2.8.1/start.html - command: standalone - ports: - - "8080:8080" - healthcheck: - test: ["CMD", "curl", "--fail", "http://localhost:8080/health"] - interval: 10s - timeout: 60s - start_period: 60s - retries: 5 - restart: always - environment: - <<: *airflow-common-env - # Analytics Tools superset: image: apache/superset:latest diff --git a/jupyter/__init__.py b/jupyter/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/pyproject.toml b/pyproject.toml index d95e2c36..5ff8afb3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,9 @@ readme = "README.md" python = "~3.11" apache-airflow = "2.8.1" apache-airflow-providers-postgres = "5.14.0" +flask-session = "0.5.0" +numpy = "1.26.4" +flask = "*" dbt-core = "*" dbt-postgres = "*" pandas = "*" @@ -17,6 +20,7 @@ requests = "*" sqlalchemy = "*" zeep = "*" imap-tools = "*" +astronomer-cosmos = "*" [tool.poetry.group.dev.dependencies] black = "*" @@ -26,10 +30,28 @@ pytest-cov = "*" mypy = "*" types-psycopg2 = "*" pandas-stubs = "*" +sqlalchemy-stubs = "*" jupyter = "*" shandy-sqlfmt = "*" sqlfluff = "*" sqlfluff-templater-dbt = "*" +types-Authlib = "*" +types-Deprecated = "*" +types-Pygments = "*" +types-WTForms = "*" +types-croniter = "*" +types-gevent = "*" +types-jmespath = "*" +types-jsonschema = "*" +types-openpyxl = "*" +types-psutil = "*" +types-tabulate = "*" +types-colorama = "*" +types-decorator = "*" +types-passlib = "*" +types-pycurl = "*" +types-simplejson = "*" +types-uWSGI = "*" [build-system] requires = ["poetry-core"] @@ -86,7 +108,8 @@ warn_redundant_casts = true warn_unused_ignores = true warn_no_return = true warn_unreachable = true -disable_error_code = ["arg-type"] +follow_untyped_imports = true +disable_error_code = ["arg-type", "attr-defined"] [tool.pytest.ini_options] testpaths = ["tests"] @@ -100,18 +123,23 @@ exclude = [ ".venv/", "build/", "dist/", + "**/target/**", + "**/compiled/**" ] [tool.sqlfluff.core] dialect = "postgres" templater = "dbt" max_line_length = 90 -exclude_rules = "L016" +exclude_rules = "L016,LT02,LT09,LT14,RF02,AL04,CV08" +verbose = 1 [tool.sqlfluff.indentation] indented_joins = true indented_using_on = true template_blocks_indent = true +indent_unit = "space" +tab_space_size = 2 [tool.sqlfluff.rules.capitalisation.keywords] capitalisation_policy = "lower" @@ -129,7 +157,10 @@ capitalisation_policy = "lower" unwrap_wrapped_queries = true [tool.sqlfluff.templater.dbt] -project_dir = "." -profiles_dir = "." -profile = "default" -target = "dev" +project_dir = "./airflow_lappis/dags/dbt/ipea" +profiles_dir = "./airflow_lappis/dags/dbt/ipea" +profile = "ipea" +target = "prod" +defer_mode = true +static_analysis = true +disable_database_connection = true diff --git a/requirements.txt b/requirements.txt index cfe3a74c..eeb08c41 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ +aenum==3.1.15 ; python_version >= "3.11" and python_version < "3.12" agate==1.9.1 ; python_version >= "3.11" and python_version < "3.12" -aiohappyeyeballs==2.4.4 ; python_version >= "3.11" and python_version < "3.12" +aiohappyeyeballs==2.4.6 ; python_version >= "3.11" and python_version < "3.12" aiohttp==3.10.11 ; python_version >= "3.11" and python_version < "3.12" aiosignal==1.3.2 ; python_version >= "3.11" and python_version < "3.12" alembic==1.14.1 ; python_version >= "3.11" and python_version < "3.12" @@ -16,10 +17,11 @@ apache-airflow==2.8.1 ; python_version >= "3.11" and python_version < "3.12" apispec[yaml]==6.8.1 ; python_version >= "3.11" and python_version < "3.12" argcomplete==3.5.3 ; python_version >= "3.11" and python_version < "3.12" asgiref==3.8.1 ; python_version >= "3.11" and python_version < "3.12" +astronomer-cosmos==1.8.2 ; python_version >= "3.11" and python_version < "3.12" attrs==25.1.0 ; python_version >= "3.11" and python_version < "3.12" babel==2.17.0 ; python_version >= "3.11" and python_version < "3.12" blinker==1.9.0 ; python_version >= "3.11" and python_version < "3.12" -cachelib==0.9.0 ; python_version >= "3.11" and python_version < "3.12" +cachelib==0.13.0 ; python_version >= "3.11" and python_version < "3.12" certifi==2025.1.31 ; python_version >= "3.11" and python_version < "3.12" cffi==1.17.1 ; python_version >= "3.11" and python_version < "3.12" and platform_python_implementation != "PyPy" charset-normalizer==3.4.1 ; python_version >= "3.11" and python_version < "3.12" @@ -31,10 +33,10 @@ configupdater==3.2 ; python_version >= "3.11" and python_version < "3.12" connexion[flask]==2.14.1 ; python_version >= "3.11" and python_version < "3.12" cron-descriptor==1.4.5 ; python_version >= "3.11" and python_version < "3.12" croniter==6.0.0 ; python_version >= "3.11" and python_version < "3.12" -cryptography==44.0.0 ; python_version >= "3.11" and python_version < "3.12" +cryptography==44.0.1 ; python_version >= "3.11" and python_version < "3.12" daff==1.3.46 ; python_version >= "3.11" and python_version < "3.12" -dbt-adapters==1.13.2 ; python_version >= "3.11" and python_version < "3.12" -dbt-common==1.14.0 ; python_version >= "3.11" and python_version < "3.12" +dbt-adapters==1.14.0 ; python_version >= "3.11" and python_version < "3.12" +dbt-common==1.15.0 ; python_version >= "3.11" and python_version < "3.12" dbt-core==1.9.2 ; python_version >= "3.11" and python_version < "3.12" dbt-extractor==0.5.1 ; python_version >= "3.11" and python_version < "3.12" dbt-postgres==1.9.0 ; python_version >= "3.11" and python_version < "3.12" @@ -42,22 +44,24 @@ dbt-semantic-interfaces==0.7.4 ; python_version >= "3.11" and python_version < " deepdiff==7.0.1 ; python_version >= "3.11" and python_version < "3.12" deprecated==1.2.18 ; python_version >= "3.11" and python_version < "3.12" dill==0.3.9 ; python_version >= "3.11" and python_version < "3.12" +distlib==0.3.9 ; python_version >= "3.11" and python_version < "3.12" dnspython==2.7.0 ; python_version >= "3.11" and python_version < "3.12" email-validator==1.3.1 ; python_version >= "3.11" and python_version < "3.12" +filelock==3.17.0 ; python_version >= "3.11" and python_version < "3.12" flask-appbuilder==4.3.10 ; python_version >= "3.11" and python_version < "3.12" flask-babel==2.0.0 ; python_version >= "3.11" and python_version < "3.12" -flask-caching==2.3.0 ; python_version >= "3.11" and python_version < "3.12" +flask-caching==2.0.1 ; python_version >= "3.11" and python_version < "3.12" flask-jwt-extended==4.7.1 ; python_version >= "3.11" and python_version < "3.12" flask-limiter==3.10.1 ; python_version >= "3.11" and python_version < "3.12" flask-login==0.6.3 ; python_version >= "3.11" and python_version < "3.12" -flask-session==0.8.0 ; python_version >= "3.11" and python_version < "3.12" +flask-session==0.5.0 ; python_version >= "3.11" and python_version < "3.12" flask-sqlalchemy==2.5.1 ; python_version >= "3.11" and python_version < "3.12" flask-wtf==1.2.2 ; python_version >= "3.11" and python_version < "3.12" flask==2.2.5 ; python_version >= "3.11" and python_version < "3.12" frozenlist==1.5.0 ; python_version >= "3.11" and python_version < "3.12" fsspec==2025.2.0 ; python_version >= "3.11" and python_version < "3.12" google-re2==1.1.20240702 ; python_version >= "3.11" and python_version < "3.12" -googleapis-common-protos==1.66.0 ; python_version >= "3.11" and python_version < "3.12" +googleapis-common-protos==1.67.0 ; python_version >= "3.11" and python_version < "3.12" greenlet==3.1.1 ; python_version >= "3.11" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32") and python_version < "3.12" grpcio==1.70.0 ; python_version >= "3.11" and python_version < "3.12" gunicorn==23.0.0 ; python_version >= "3.11" and python_version < "3.12" @@ -65,7 +69,7 @@ h11==0.14.0 ; python_version >= "3.11" and python_version < "3.12" httpcore==1.0.7 ; python_version >= "3.11" and python_version < "3.12" httpx==0.28.1 ; python_version >= "3.11" and python_version < "3.12" idna==3.10 ; python_version >= "3.11" and python_version < "3.12" -imap-tools==1.9.1 ; python_version >= "3.11" and python_version < "3.12" +imap-tools==1.10.0 ; python_version >= "3.11" and python_version < "3.12" importlib-metadata==6.11.0 ; python_version >= "3.11" and python_version < "3.12" inflection==0.5.1 ; python_version >= "3.11" and python_version < "3.12" isodate==0.6.1 ; python_version >= "3.11" and python_version < "3.12" @@ -78,7 +82,7 @@ leather==0.4.0 ; python_version >= "3.11" and python_version < "3.12" limits==4.0.1 ; python_version >= "3.11" and python_version < "3.12" linkify-it-py==2.0.3 ; python_version >= "3.11" and python_version < "3.12" lockfile==0.12.2 ; python_version >= "3.11" and python_version < "3.12" -lxml==5.3.0 ; python_version >= "3.11" and python_version < "3.12" +lxml==5.3.1 ; python_version >= "3.11" and python_version < "3.12" mako==1.3.9 ; python_version >= "3.11" and python_version < "3.12" markdown-it-py==3.0.0 ; python_version >= "3.11" and python_version < "3.12" markdown==3.7 ; python_version >= "3.11" and python_version < "3.12" @@ -91,10 +95,9 @@ mdit-py-plugins==0.4.2 ; python_version >= "3.11" and python_version < "3.12" mdurl==0.1.2 ; python_version >= "3.11" and python_version < "3.12" more-itertools==10.6.0 ; python_version >= "3.11" and python_version < "3.12" msgpack==1.1.0 ; python_version >= "3.11" and python_version < "3.12" -msgspec==0.19.0 ; python_version >= "3.11" and python_version < "3.12" multidict==6.1.0 ; python_version >= "3.11" and python_version < "3.12" networkx==3.4.2 ; python_version >= "3.11" and python_version < "3.12" -numpy==2.2.2 ; python_version == "3.11" +numpy==1.26.4 ; python_version >= "3.11" and python_version < "3.12" opentelemetry-api==1.30.0 ; python_version >= "3.11" and python_version < "3.12" opentelemetry-exporter-otlp-proto-common==1.30.0 ; python_version >= "3.11" and python_version < "3.12" opentelemetry-exporter-otlp-proto-grpc==1.30.0 ; python_version >= "3.11" and python_version < "3.12" @@ -114,7 +117,7 @@ pluggy==1.5.0 ; python_version >= "3.11" and python_version < "3.12" prison==0.2.1 ; python_version >= "3.11" and python_version < "3.12" propcache==0.2.1 ; python_version >= "3.11" and python_version < "3.12" protobuf==5.29.3 ; python_version >= "3.11" and python_version < "3.12" -psutil==6.1.1 ; python_version >= "3.11" and python_version < "3.12" +psutil==7.0.0 ; python_version >= "3.11" and python_version < "3.12" psycopg2-binary==2.9.10 ; python_version >= "3.11" and python_version < "3.12" pycparser==2.22 ; python_version >= "3.11" and python_version < "3.12" and platform_python_implementation != "PyPy" pydantic-core==2.27.2 ; python_version >= "3.11" and python_version < "3.12" @@ -133,7 +136,7 @@ requests-file==2.1.0 ; python_version >= "3.11" and python_version < "3.12" requests-toolbelt==1.0.0 ; python_version >= "3.11" and python_version < "3.12" requests==2.32.3 ; python_version >= "3.11" and python_version < "3.12" rfc3339-validator==0.1.4 ; python_version >= "3.11" and python_version < "3.12" -rich-argparse==1.6.0 ; python_version >= "3.11" and python_version < "3.12" +rich-argparse==1.7.0 ; python_version >= "3.11" and python_version < "3.12" rich==13.9.4 ; python_version >= "3.11" and python_version < "3.12" rpds-py==0.22.3 ; python_version >= "3.11" and python_version < "3.12" setproctitle==1.3.4 ; python_version >= "3.11" and python_version < "3.12" @@ -155,6 +158,7 @@ uc-micro-py==1.0.3 ; python_version >= "3.11" and python_version < "3.12" unicodecsv==0.14.1 ; python_version >= "3.11" and python_version < "3.12" universal-pathlib==0.2.6 ; python_version >= "3.11" and python_version < "3.12" urllib3==2.3.0 ; python_version >= "3.11" and python_version < "3.12" +virtualenv==20.29.2 ; python_version >= "3.11" and python_version < "3.12" werkzeug==2.3.8 ; python_version >= "3.11" and python_version < "3.12" wrapt==1.17.2 ; python_version >= "3.11" and python_version < "3.12" wtforms==3.2.1 ; python_version >= "3.11" and python_version < "3.12" From 90c0b20c8eac8f827ba04f33aaa9d0a04534ff1d Mon Sep 17 00:00:00 2001 From: Mateus de Castro Date: Wed, 19 Feb 2025 20:26:51 +0000 Subject: [PATCH 014/317] feat(cronograma):adicionei inserted at dentro cronogramas empenhos e faturas e... --- airflow_lappis/dags/dbt/ipea/dbt_project.yml | 1 + .../ipea/models/contratos/bronze/cronogramas.sql | 14 ++++++++++++++ .../dbt/ipea/models/contratos/bronze/empenhos.sql | 3 ++- .../dbt/ipea/models/contratos/bronze/faturas.sql | 3 ++- .../models/contratos/bronze/identificadores.sql | 2 ++ airflow_lappis/dags/dbt/ipea/models/sources.yml | 2 +- 6 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 airflow_lappis/dags/dbt/ipea/models/contratos/bronze/cronogramas.sql diff --git a/airflow_lappis/dags/dbt/ipea/dbt_project.yml b/airflow_lappis/dags/dbt/ipea/dbt_project.yml index 35338d41..0c51379a 100755 --- a/airflow_lappis/dags/dbt/ipea/dbt_project.yml +++ b/airflow_lappis/dags/dbt/ipea/dbt_project.yml @@ -25,6 +25,7 @@ models: +schema: contratos bronze: +materialized: incremental + +unique_key: id pessoas: +materialized: table +database: analytics diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/cronogramas.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/cronogramas.sql new file mode 100644 index 00000000..88553283 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/cronogramas.sql @@ -0,0 +1,14 @@ +select + id::integer as id, + contrato_id::integer as contrato_id, + tipo::text as tipo, + numero::text as numero, + receita_despesa::text as receita_despesa, + observacao::text as observacao, + mesref::integer as mesref, + anoref::integer as anoref, + retroativo::text as retroativo, + replace(replace(valor::text, '.', ''), ',', '.')::numeric(15, 2) as valor, + vencimento::date as vencimento, + now() as inserted_at +from {{ source("compras_gov", "cronograma") }} diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos.sql index 6c8a5d5d..7a0ec395 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos.sql @@ -47,7 +47,8 @@ with and data_emissao::text ~ '^\d{4}-\d{2}-\d{2}$' -- Retorna NULL se não for uma data válida then to_date(data_emissao::text, 'YYYY-MM-DD') - end as data_emissao + end as data_emissao, + now() as inserted_at from {{ source("compras_gov", "empenhos") }} ) diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/faturas.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/faturas.sql index e467ac15..b72d5db1 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/faturas.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/faturas.sql @@ -40,7 +40,8 @@ with f.dados_empenho ->> 'id_empenho' as id_empenho, upper(f.dados_empenho ->> 'numero_empenho') as numero_empenho, f.dados_empenho ->> 'valor_empenho' as valor_empenho, - f.dados_empenho ->> 'subelemento' as subelemento + f.dados_empenho ->> 'subelemento' as subelemento, + now() as inserted_at from faturas_raw as f ) diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/identificadores.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/identificadores.sql index 80e195d8..cf9ebe26 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/identificadores.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/identificadores.sql @@ -1,3 +1,5 @@ +{{ config(materialized="table") }} + with ids_from_empenhos as ( diff --git a/airflow_lappis/dags/dbt/ipea/models/sources.yml b/airflow_lappis/dags/dbt/ipea/models/sources.yml index d5eac843..afb7fd1e 100644 --- a/airflow_lappis/dags/dbt/ipea/models/sources.yml +++ b/airflow_lappis/dags/dbt/ipea/models/sources.yml @@ -7,7 +7,7 @@ sources: - name: contratos - name: faturas - name: empenhos - - name: cronogramas + - name: cronograma - name: siafi schema: siafi From b076e93365a181a90ac819f5d8d8932fed00bd0b Mon Sep 17 00:00:00 2001 From: arthrok Date: Thu, 20 Feb 2025 19:39:44 -0300 Subject: [PATCH 015/317] fix(cosmos): adiciona nova env com o path do airflow e ajusta para o cosmos --- airflow_lappis/dags/dbt/ipea/cosmos_dag.py | 6 +++--- docker-compose.yml | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/airflow_lappis/dags/dbt/ipea/cosmos_dag.py b/airflow_lappis/dags/dbt/ipea/cosmos_dag.py index c335e582..af77f3eb 100755 --- a/airflow_lappis/dags/dbt/ipea/cosmos_dag.py +++ b/airflow_lappis/dags/dbt/ipea/cosmos_dag.py @@ -3,16 +3,16 @@ from cosmos import DbtDag, ProjectConfig, ProfileConfig, ExecutionConfig profile_config = ProfileConfig( - profiles_yml_filepath=f"{os.environ['AIRFLOW_HOME']}/dags/dbt/ipea/profiles.yml", + profiles_yml_filepath=f"{os.environ['AIRFLOW_REPO_BASE']}/dags/dbt/ipea/profiles.yml", profile_name="ipea", target_name="prod", ) my_cosmos_dag = DbtDag( - project_config=ProjectConfig(f"{os.environ['AIRFLOW_HOME']}/dags/dbt/ipea"), + project_config=ProjectConfig(f"{os.environ['AIRFLOW_REPO_BASE']}/dags/dbt/ipea"), profile_config=profile_config, execution_config=ExecutionConfig( - dbt_executable_path=f"{os.environ['AIRFLOW_HOME']}/.local/bin/dbt", + dbt_executable_path=f"{os.environ['AIRFLOW_REPO_BASE']}/.local/bin/dbt", ), schedule_interval="@daily", start_date=datetime(2025, 1, 1), diff --git a/docker-compose.yml b/docker-compose.yml index 10dad517..d8a7ec4c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -38,7 +38,11 @@ x-airflow-environment: &airflow-common-env _AIRFLOW_WWW_USER_PASSWORD: ${_AIRFLOW_WWW_USER_PASSWORD:-airflow} AIRFLOW__CORE__PLUGINS_FOLDER: ${AIRFLOW_HOME}/plugins AIRFLOW__CORE__DAGS_FOLDER: ${AIRFLOW_HOME}/dags - + AIRFLOW_REPO_BASE: ${AIRFLOW_HOME} + # A ENV AIRFLOW_REPO_BASE É IMPORTANTE PARA SINCRONIZAR COM O SISTEMA DE PASTAS + # DO AIRFLOW EM HOMOLOG E PROD, ELES POSSUEM UMA ESTRUTURA DE PASTAS DIFERENTE + # USAR ESSA ENV NOS CÓDIGOS PARA NÃO HAVER CONFLITOS + services: # Airflow Services airflow: From 387ec2a4900142bf66b209bd06ca3a41c875cb9a Mon Sep 17 00:00:00 2001 From: VictorSzk Date: Sun, 23 Feb 2025 19:03:40 +0000 Subject: [PATCH 016/317] fix(dbt): consertando nome de colunas --- airflow_lappis/dags/dbt/ipea/dbt_project.yml | 1 - .../models/contratos/bronze/contratos.sql | 48 ++++++++----------- .../ipea/models/contratos/bronze/empenhos.sql | 8 ++-- .../contratos/bronze/empenhos_tesouro.sql | 1 - .../ipea/models/contratos/bronze/estagios.sql | 3 +- .../ipea/models/contratos/bronze/faturas.sql | 9 ++-- .../contratos/bronze/identificadores.sql | 13 ++--- .../contratos/gold/contratos_resumo.sql | 6 +-- .../dags/dbt/ipea/models/sources.yml | 2 +- .../dbt/ipea/snapshots/contratos_snapshot.yml | 2 +- 10 files changed, 40 insertions(+), 53 deletions(-) diff --git a/airflow_lappis/dags/dbt/ipea/dbt_project.yml b/airflow_lappis/dags/dbt/ipea/dbt_project.yml index 0c51379a..35338d41 100755 --- a/airflow_lappis/dags/dbt/ipea/dbt_project.yml +++ b/airflow_lappis/dags/dbt/ipea/dbt_project.yml @@ -25,7 +25,6 @@ models: +schema: contratos bronze: +materialized: incremental - +unique_key: id pessoas: +materialized: table +database: analytics diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/contratos.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/contratos.sql index a22bb83f..dd290759 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/contratos.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/contratos.sql @@ -7,31 +7,25 @@ with cast(id as text) as id, receita_despesa, numero, - cast( - contratante_orgao_origem_codigo as int - ) as contratante_orgao_origem_codigo, - contratante_orgao_origem_nome, - cast( - contratante_orgao_origem_unidade_gestora_origem_codigo as int - ) as contratante_orgao_origem_unidade_gestora_origem_codigo, - contratante_orgao_origem_unidade_gestora_origem_nome_resumido, - contratante_orgao_origem_unidade_gestora_origem_nome, - contratante_orgao_origem_unidade_gestora_origem_sisg, - contratante_orgao_origem_unidade_gestora_origem_utiliza_siafi, - contratante_orgao_origem_unidade_gestora_origem_utiliza_antecip, - cast(contratante_orgao_codigo as int) as contratante_orgao_codigo, - contratante_orgao_nome, - cast( - contratante_orgao_unidade_gestora_codigo as int - ) as contratante_orgao_unidade_gestora_codigo, - contratante_orgao_unidade_gestora_nome_resumido, - contratante_orgao_unidade_gestora_nome, - contratante_orgao_unidade_gestora_sisg, - contratante_orgao_unidade_gestora_utiliza_siafi, - contratante_orgao_unidade_gestora_utiliza_antecipagov, - fornecedor_tipo, - fornecedor_nome, - cast(codigo_tipo as int) as codigo_tipo, + contratante__orgao_origem__codigo, + contratante__orgao_origem__nome, + contratante__orgao_origem__unidade_gestora_origem__codigo, + contratante__orgao_origem__unidade_gestora_origem__nome_resumido, + contratante__orgao_origem__unidade_gestora_origem__nome, + contratante__orgao_origem__unidade_gestora_origem__sisg, + contratante__orgao_origem__unidade_gestora_origem__utiliza_siafi, + contratante__orgao_origem__unidade_gestora_origem__utiliza_antecip, + contratante__orgao__codigo, + contratante__orgao__nome, + contratante__orgao__unidade_gestora__codigo, + contratante__orgao__unidade_gestora__nome_resumido, + contratante__orgao__unidade_gestora__nome, + contratante__orgao__unidade_gestora__sisg, + contratante__orgao__unidade_gestora__utiliza_siafi, + contratante__orgao__unidade_gestora__utiliza_antecipagov, + fornecedor__tipo as fornecedor_tipo, + fornecedor__nome as fornecedor_nome, + codigo_tipo as codigo_tipo, tipo, subtipo, prorrogavel, @@ -45,7 +39,7 @@ with informacao_complementar, codigo_modalidade, modalidade, - cast(unidade_compra as int) as unidade_compra, + unidade_compra as unidade_compra, licitacao_numero, sistema_origem_licitacao, cast(num_parcelas as int) as num_parcelas, @@ -71,7 +65,7 @@ with ) as numeric(15, 2) ) as valor_acumulado, regexp_replace( - fornecedor_cnpj_cpf_idgener, '[^0-9A-Za-z]', '', 'g' + fornecedor__cnpj_cpf_idgener, '[^0-9A-Za-z]', '', 'g' ) as fornecedor_cnpj_cpf_idgener, regexp_replace(processo, '[^0-9A-Za-z]', '', 'g') as processo, -- Conversão de valores numéricos para FLOAT ou INT diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos.sql index 7a0ec395..e6d0f50f 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos.sql @@ -13,10 +13,10 @@ with naturezadespesa, informacao_complementar, sistema_origem, - links_documento_pagamento, - credor_obj_tipo, - credor_obj_cnpj_cpf_idgener, - credor_obj_nome, + links__documento_pagamento as links_documento_pagamento, + credor_obj__tipo as credor_obj_tipo, + credor_obj__cnpj_cpf_idgener as credor_obj_cnpj_cpf_idgener, + credor_obj__nome as credor_obj_nome, -- Tratar valores nulos ou inválidos nas colunas de data replace(replace(empenhado::text, '.', ''), ',', '.')::numeric( diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql index d645f5fd..fc9b2fa1 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql @@ -2,7 +2,6 @@ with empenhos_raw as ( select - id::integer as id, ne_ccor::text as ne_ccor, ne_informacao_complementar::text as ne_informacao_complementar, ne_ccor_descricao::text as ne_ccor_descricao, diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/estagios.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/estagios.sql index 25512a60..ca73d292 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/estagios.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/estagios.sql @@ -12,7 +12,6 @@ with estagios_raw as ( select - id::integer as id, ne_ccor, ne_informacao_complementar::text, @@ -71,7 +70,7 @@ with despesas_pagas_controle_empenho_movim_liquido_moeda_origem ) as despesas_pagas_controle_empenho_movim_liquido_moeda_origem - from {{ source("siafi", "estagios") }} + from {{ source("siafi", "estagios_tesouro") }} ) select * diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/faturas.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/faturas.sql index b72d5db1..30fc301b 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/faturas.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/faturas.sql @@ -29,10 +29,11 @@ with chave_nfe::text as chave_nfe, dados_referencia::text as dados_referencia, dados_item_faturado::text as dados_item_faturado, - jsonb_array_elements(dados_empenho::jsonb) as dados_empenho + jsonb_array_elements( + replace(dados_empenho, '''', '"')::jsonb + ) as dados_empenho from {{ source("compras_gov", "faturas") }} ), - -- Extrai os campos do JSON e transforma em colunas individuais faturas_dados_empenho as ( select @@ -43,8 +44,6 @@ with f.dados_empenho ->> 'subelemento' as subelemento, now() as inserted_at from faturas_raw as f - ) - --- + ) -- select * from faturas_dados_empenho diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/identificadores.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/identificadores.sql index cf9ebe26..ecc0f6ae 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/identificadores.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/identificadores.sql @@ -13,12 +13,9 @@ with ), ids_table as ( - select contrato_id, ne - from ids_from_empenhos - full join - ids_from_faturas - on ids_from_empenhos.contrato_id = ids_from_faturas.contrato_id - and ids_from_empenhos.ne = ids_from_faturas.ne + select * + from ids_from_empenhos e + full join ids_from_faturas f using (contrato_id, ne) ), contratos as ( @@ -31,14 +28,14 @@ with when codigo_modalidade in ('05', '06') then concat( - contratante_orgao_unidade_gestora_codigo, + contratante__orgao__unidade_gestora__codigo, codigo_modalidade, replace(numero, '/', '') ) when codigo_modalidade = '07' then concat( - contratante_orgao_unidade_gestora_codigo, + contratante__orgao__unidade_gestora__codigo, codigo_modalidade, replace(licitacao_numero, '/', '') ) diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/gold/contratos_resumo.sql b/airflow_lappis/dags/dbt/ipea/models/contratos/gold/contratos_resumo.sql index e76bc716..5c32781d 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos/gold/contratos_resumo.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos/gold/contratos_resumo.sql @@ -14,7 +14,7 @@ with when vp.despesas_pagas = c.valor_global then 'Sim' else 'Não' end as pendente_baixa from {{ ref("contratos") }} as c - left join valores_pagos_contratos as vp on c.id = vp.id + left join valores_pagos_contratos as vp using (id) ) -- @@ -40,9 +40,9 @@ select else fornecedor_tipo end as fornecedor_tipo, concat( - contratante_orgao_origem_unidade_gestora_origem_codigo, + contratante__orgao_origem__unidade_gestora_origem__codigo, ' - ', - contratante_orgao_origem_unidade_gestora_origem_nome_resumido + contratante__orgao_origem__unidade_gestora_origem__nome_resumido ) as "Unidade", case when vigencia_fim - vigencia_inicio >= 730 and num_parcelas > 1 diff --git a/airflow_lappis/dags/dbt/ipea/models/sources.yml b/airflow_lappis/dags/dbt/ipea/models/sources.yml index afb7fd1e..85583457 100644 --- a/airflow_lappis/dags/dbt/ipea/models/sources.yml +++ b/airflow_lappis/dags/dbt/ipea/models/sources.yml @@ -13,5 +13,5 @@ sources: schema: siafi tables: - name: empenhos_tesouro - - name: estagios + - name: estagios_tesouro diff --git a/airflow_lappis/dags/dbt/ipea/snapshots/contratos_snapshot.yml b/airflow_lappis/dags/dbt/ipea/snapshots/contratos_snapshot.yml index 30ef5d79..e637d5ec 100644 --- a/airflow_lappis/dags/dbt/ipea/snapshots/contratos_snapshot.yml +++ b/airflow_lappis/dags/dbt/ipea/snapshots/contratos_snapshot.yml @@ -7,4 +7,4 @@ snapshots: unique_key: id strategy: timestamp updated_at: updated_at - dbt_valid_to_current: "to_date('9999-12-31', 'YYYY-MM-DD')" \ No newline at end of file + dbt_valid_to_current: "to_timestamp('9999-12-31', 'YYYY-MM-DD')" \ No newline at end of file From f26051cdda9e265a8df50ca7b68c41d9a66fd3ef Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Wed, 26 Feb 2025 14:26:15 +0000 Subject: [PATCH 017/317] Feat/testes unitarios --- Makefile | 2 +- airflow_lappis/plugins/cliente_base.py | 2 +- airflow_lappis/plugins/cliente_email.py | 1 - airflow_lappis/plugins/cliente_estrutura.py | 4 +- airflow_lappis/plugins/cliente_postgres.py | 1 + tests/conftest.py | 7 ++ tests/test_cliente_base/test_request.py | 40 +++++++++ .../test_get_contratos.py | 62 +++++++++++++ .../test_get_contratos_inativos.py | 62 +++++++++++++ .../test_get_cronograma.py | 49 ++++++++++ .../test_get_empenhos.py | 49 ++++++++++ .../test_get_faturas.py | 49 ++++++++++ .../test_cliente_email/test_cliente_email.py | 49 ++++++++++ tests/test_cliente_email/test_extract_csv.py | 36 ++++++++ .../test_cliente_email/test_process_email.py | 54 +++++++++++ .../test_get_estrutura.py | 89 +++++++++++++++++++ .../test_create_table.py | 31 +++++++ .../test_cliente_postgres/test_drop_table.py | 71 +++++++++++++++ .../test_flatten_data.py | 43 +++++++++ .../test_get_contratos_ids.py | 28 ++++++ .../test_insert_csv_data.py | 42 +++++++++ .../test_cliente_postgres/test_insert_data.py | 45 ++++++++++ 22 files changed, 811 insertions(+), 5 deletions(-) create mode 100644 tests/conftest.py create mode 100644 tests/test_cliente_base/test_request.py create mode 100644 tests/test_cliente_contratos/test_get_contratos.py create mode 100644 tests/test_cliente_contratos/test_get_contratos_inativos.py create mode 100644 tests/test_cliente_contratos/test_get_cronograma.py create mode 100644 tests/test_cliente_contratos/test_get_empenhos.py create mode 100644 tests/test_cliente_contratos/test_get_faturas.py create mode 100644 tests/test_cliente_email/test_cliente_email.py create mode 100644 tests/test_cliente_email/test_extract_csv.py create mode 100644 tests/test_cliente_email/test_process_email.py create mode 100644 tests/test_cliente_estrutura/test_get_estrutura.py create mode 100644 tests/test_cliente_postgres/test_create_table.py create mode 100644 tests/test_cliente_postgres/test_drop_table.py create mode 100644 tests/test_cliente_postgres/test_flatten_data.py create mode 100644 tests/test_cliente_postgres/test_get_contratos_ids.py create mode 100644 tests/test_cliente_postgres/test_insert_csv_data.py create mode 100644 tests/test_cliente_postgres/test_insert_data.py diff --git a/Makefile b/Makefile index 58b5b6a6..71bd4813 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ format: lint: poetry run black . --check poetry run ruff check . - poetry run mypy . --explicit-package-bases --install-types + poetry run mypy airflow_lappis --explicit-package-bases --install-types poetry run sqlfmt ./airflow_lappis/dags/dbt --check [ "${GITLAB_CI}" ] || poetry run sqlfluff lint ./airflow_lappis/dags/dbt diff --git a/airflow_lappis/plugins/cliente_base.py b/airflow_lappis/plugins/cliente_base.py index 3f8e6269..6a293897 100755 --- a/airflow_lappis/plugins/cliente_base.py +++ b/airflow_lappis/plugins/cliente_base.py @@ -52,7 +52,7 @@ def request( f"[cliente_base.py] API failed with status {status} on " f"attempt {attempt + 1}. Error: {str(e)}" ) - if attempt < self.DEFAULT_MAX_RETRIES: + if attempt < self.DEFAULT_MAX_RETRIES - 1: time.sleep(attempt**2 * self.DEFAULT_SLEEP_SECONDS) else: logging.error( diff --git a/airflow_lappis/plugins/cliente_email.py b/airflow_lappis/plugins/cliente_email.py index 0f079c12..af97f449 100755 --- a/airflow_lappis/plugins/cliente_email.py +++ b/airflow_lappis/plugins/cliente_email.py @@ -3,7 +3,6 @@ import zipfile from typing import Optional from typing_extensions import Buffer - import pandas as pd from imap_tools import MailBox, AND, MailMessage import chardet diff --git a/airflow_lappis/plugins/cliente_estrutura.py b/airflow_lappis/plugins/cliente_estrutura.py index 7522b657..e2a61ef4 100755 --- a/airflow_lappis/plugins/cliente_estrutura.py +++ b/airflow_lappis/plugins/cliente_estrutura.py @@ -39,7 +39,7 @@ def get_estrutura_organizacional_resumida( status, data = self.request(http.HTTPMethod.GET, endpoint, params=params) return ( - data.get("unidades", []) - if status == http.HTTPStatus.OK and type(data) is dict + data.get("unidades") + if status == http.HTTPStatus.OK and type(data) is dict and "unidades" in data else None ) diff --git a/airflow_lappis/plugins/cliente_postgres.py b/airflow_lappis/plugins/cliente_postgres.py index 6dc1690b..eb4f77fa 100755 --- a/airflow_lappis/plugins/cliente_postgres.py +++ b/airflow_lappis/plugins/cliente_postgres.py @@ -4,6 +4,7 @@ from pandas import json_normalize import pandas as pd import io +import psycopg2.extras class ClientPostgresDB: diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..b36e5a36 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,7 @@ +import sys +import os + +sys.path.insert( + 0, + os.path.abspath(os.path.join(os.path.dirname(__file__), "../airflow_lappis/plugins")), +) diff --git a/tests/test_cliente_base/test_request.py b/tests/test_cliente_base/test_request.py new file mode 100644 index 00000000..6855c63a --- /dev/null +++ b/tests/test_cliente_base/test_request.py @@ -0,0 +1,40 @@ +import pytest +from unittest.mock import patch, Mock +from http import HTTPStatus +import httpx +from airflow_lappis.plugins.cliente_base import ClienteBase + + +@pytest.fixture +def cliente_base(): + return ClienteBase(base_url="http://example.com") + + +def test_request_success(cliente_base): + # Configura o mock para retornar uma resposta bem-sucedida + mock_response = Mock() + mock_response.status_code = HTTPStatus.OK + mock_response.json.return_value = {"key": "value"} + mock_response.raise_for_status.return_value = None + + with patch("httpx.Client.request", return_value=mock_response): + # Chama o método request + status, response = cliente_base.request("GET", "/test") + + # Verifica se o status e a resposta são os esperados + assert status == HTTPStatus.OK + assert response == {"key": "value"} + + +def test_request_failure(cliente_base): + # Configura o mock para simular uma exceção HTTPError + mock_response = Mock() + mock_response.status_code = HTTPStatus.BAD_REQUEST + mock_response.raise_for_status.side_effect = httpx.HTTPError("Bad Request") + + with patch("httpx.Client.request", return_value=mock_response): + # Chama o método request e verifica se a exceção é levantada após as tentativas + with pytest.raises( + Exception, match="API failed after the maximum number of attempts!" + ): + cliente_base.request("GET", "/test") diff --git a/tests/test_cliente_contratos/test_get_contratos.py b/tests/test_cliente_contratos/test_get_contratos.py new file mode 100644 index 00000000..85947298 --- /dev/null +++ b/tests/test_cliente_contratos/test_get_contratos.py @@ -0,0 +1,62 @@ +import pytest +from unittest.mock import patch, Mock +from http import HTTPStatus +from airflow_lappis.plugins.cliente_contratos import ClienteContratos + + +@pytest.fixture +def cliente_contratos(): + return ClienteContratos() + + +def test_get_contratos_by_ug_success(cliente_contratos): + # Configura o mock para retornar uma resposta bem-sucedida + mock_response = Mock() + mock_response.status_code = HTTPStatus.OK + mock_response.json.return_value = [{"id": 1, "ug_code": "12345"}] + + with patch( + "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", + return_value=(HTTPStatus.OK, mock_response.json.return_value), + ): + # Chama o método get_contratos_by_ug + result = cliente_contratos.get_contratos_by_ug("12345") + + # Verifica se o resultado é o esperado + assert result == [{"id": 1, "ug_code": "12345"}] + + +def test_get_contratos_by_ug_failure(cliente_contratos): + # Configura o mock para retornar uma resposta de falha + mock_response = Mock() + mock_response.status_code = HTTPStatus.NOT_FOUND + mock_response.json.return_value = None + + with patch( + "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", + return_value=(HTTPStatus.NOT_FOUND, None), + ): + # Chama o método get_contratos_by_ug + result = cliente_contratos.get_contratos_by_ug("12345") + + # Verifica se o resultado é None (falha) + assert result is None + + +def test_get_contratos_by_ug_invalid_data(cliente_contratos): + mock_response = Mock() + mock_response.status_code = HTTPStatus.OK + mock_response.json.return_value = { + "id": 1, + "ug_code": "12345", + } # Dados inválidos (não é uma lista) + + with patch( + "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", + return_value=(HTTPStatus.OK, mock_response.json.return_value), + ): + # Chama o método get_contratos_by_ug + result = cliente_contratos.get_contratos_by_ug("12345") + + # Verifica se o resultado é None (dados inválidos) + assert result is None diff --git a/tests/test_cliente_contratos/test_get_contratos_inativos.py b/tests/test_cliente_contratos/test_get_contratos_inativos.py new file mode 100644 index 00000000..04adf66c --- /dev/null +++ b/tests/test_cliente_contratos/test_get_contratos_inativos.py @@ -0,0 +1,62 @@ +import pytest +from unittest.mock import patch, Mock +from http import HTTPStatus +from airflow_lappis.plugins.cliente_contratos import ClienteContratos + + +@pytest.fixture +def cliente_contratos(): + return ClienteContratos() + + +def test_get_contratos_inativos_by_ug_success(cliente_contratos): + # Configura o mock para retornar uma resposta bem-sucedida + mock_response = Mock() + mock_response.status_code = HTTPStatus.OK + mock_response.json.return_value = [{"id": 1, "ug_code": "12345", "status": "inativo"}] + + with patch( + "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", + return_value=(HTTPStatus.OK, mock_response.json.return_value), + ): + # Chama o método get_contratos_inativos_by_ug + result = cliente_contratos.get_contratos_inativos_by_ug("12345") + + # Verifica se o resultado é o esperado + assert result == [{"id": 1, "ug_code": "12345", "status": "inativo"}] + + +def test_get_contratos_inativos_by_ug_failure(cliente_contratos): + # Configura o mock para retornar uma resposta de falha + mock_response = Mock() + mock_response.status_code = HTTPStatus.NOT_FOUND + mock_response.json.return_value = None + + with patch( + "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", + return_value=(HTTPStatus.NOT_FOUND, None), + ): + # Chama o método get_contratos_inativos_by_ug + result = cliente_contratos.get_contratos_inativos_by_ug("12345") + + # Verifica se o resultado é None (falha) + assert result is None + + +def test_get_contratos_inativos_by_ug_invalid_data(cliente_contratos): + mock_response = Mock() + mock_response.status_code = HTTPStatus.OK + mock_response.json.return_value = { + "id": 1, + "ug_code": "12345", + } # Dados inválidos (não é uma lista) + + with patch( + "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", + return_value=(HTTPStatus.OK, mock_response.json.return_value), + ): + # Chama o método get_contratos_inativos_by_ug + result = cliente_contratos.get_contratos_inativos_by_ug("12345") + + # Verifica se o resultado é None (dados inválidos) + assert result is None diff --git a/tests/test_cliente_contratos/test_get_cronograma.py b/tests/test_cliente_contratos/test_get_cronograma.py new file mode 100644 index 00000000..806a43f3 --- /dev/null +++ b/tests/test_cliente_contratos/test_get_cronograma.py @@ -0,0 +1,49 @@ +import pytest +from unittest.mock import patch, Mock +from http import HTTPStatus +from airflow_lappis.plugins.cliente_contratos import ClienteContratos + + +@pytest.fixture +def cliente_contratos(): + return ClienteContratos() + + +def test_get_cronograma_by_contrato_id_success(cliente_contratos): + # Mockando a resposta esperada + mock_response = Mock() + mock_response.status_code = HTTPStatus.OK + mock_response.json.return_value = [ + {"id": 1, "contrato_id": "98765", "etapa": "Fundação"}, + {"id": 2, "contrato_id": "98765", "etapa": "Estrutura"}, + ] + + with patch( + "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", + return_value=(HTTPStatus.OK, mock_response.json.return_value), + ): + result = cliente_contratos.get_cronograma_by_contrato_id("98765") + assert result == [ + {"id": 1, "contrato_id": "98765", "etapa": "Fundação"}, + {"id": 2, "contrato_id": "98765", "etapa": "Estrutura"}, + ] + + +def test_get_cronograma_by_contrato_id_failure(cliente_contratos): + # Mockando uma resposta de falha + with patch( + "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", + return_value=(HTTPStatus.NOT_FOUND, None), + ): + result = cliente_contratos.get_cronograma_by_contrato_id("98765") + assert result is None + + +def test_get_cronograma_by_contrato_id_invalid_data(cliente_contratos): + # Mockando uma resposta com dados inválidos (não é uma lista) + with patch( + "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", + return_value=(HTTPStatus.OK, {"id": 1, "contrato_id": "98765"}), + ): + result = cliente_contratos.get_cronograma_by_contrato_id("98765") + assert result is None diff --git a/tests/test_cliente_contratos/test_get_empenhos.py b/tests/test_cliente_contratos/test_get_empenhos.py new file mode 100644 index 00000000..151e2eb9 --- /dev/null +++ b/tests/test_cliente_contratos/test_get_empenhos.py @@ -0,0 +1,49 @@ +import pytest +from unittest.mock import patch, Mock +from http import HTTPStatus +from airflow_lappis.plugins.cliente_contratos import ClienteContratos + + +@pytest.fixture +def cliente_contratos(): + return ClienteContratos() + + +def test_get_empenhos_by_contrato_id_success(cliente_contratos): + # Mockando a resposta esperada + mock_response = Mock() + mock_response.status_code = HTTPStatus.OK + mock_response.json.return_value = [ + {"id": 1, "contrato_id": "98765", "valor": 500.0}, + {"id": 2, "contrato_id": "98765", "valor": 750.0}, + ] + + with patch( + "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", + return_value=(HTTPStatus.OK, mock_response.json.return_value), + ): + result = cliente_contratos.get_empenhos_by_contrato_id("98765") + assert result == [ + {"id": 1, "contrato_id": "98765", "valor": 500.0}, + {"id": 2, "contrato_id": "98765", "valor": 750.0}, + ] + + +def test_get_empenhos_by_contrato_id_failure(cliente_contratos): + # Mockando uma resposta de falha + with patch( + "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", + return_value=(HTTPStatus.NOT_FOUND, None), + ): + result = cliente_contratos.get_empenhos_by_contrato_id("98765") + assert result is None + + +def test_get_empenhos_by_contrato_id_invalid_data(cliente_contratos): + # Mockando uma resposta com dados inválidos (não é uma lista) + with patch( + "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", + return_value=(HTTPStatus.OK, {"id": 1, "contrato_id": "98765"}), + ): + result = cliente_contratos.get_empenhos_by_contrato_id("98765") + assert result is None diff --git a/tests/test_cliente_contratos/test_get_faturas.py b/tests/test_cliente_contratos/test_get_faturas.py new file mode 100644 index 00000000..e9ad88b4 --- /dev/null +++ b/tests/test_cliente_contratos/test_get_faturas.py @@ -0,0 +1,49 @@ +import pytest +from unittest.mock import patch, Mock +from http import HTTPStatus +from airflow_lappis.plugins.cliente_contratos import ClienteContratos + + +@pytest.fixture +def cliente_contratos(): + return ClienteContratos() + + +def test_get_faturas_by_contrato_id_success(cliente_contratos): + # Mockando a resposta esperada + mock_response = Mock() + mock_response.status_code = HTTPStatus.OK + mock_response.json.return_value = [ + {"id": 1, "contrato_id": "98765", "valor": 100.0}, + {"id": 2, "contrato_id": "98765", "valor": 200.0}, + ] + + with patch( + "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", + return_value=(HTTPStatus.OK, mock_response.json.return_value), + ): + result = cliente_contratos.get_faturas_by_contrato_id("98765") + assert result == [ + {"id": 1, "contrato_id": "98765", "valor": 100.0}, + {"id": 2, "contrato_id": "98765", "valor": 200.0}, + ] + + +def test_get_faturas_by_contrato_id_failure(cliente_contratos): + # Mockando uma resposta de falha + with patch( + "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", + return_value=(HTTPStatus.NOT_FOUND, None), + ): + result = cliente_contratos.get_faturas_by_contrato_id("98765") + assert result is None + + +def test_get_faturas_by_contrato_id_invalid_data(cliente_contratos): + # Mockando uma resposta com dados inválidos (não é uma lista) + with patch( + "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", + return_value=(HTTPStatus.OK, {"id": 1, "contrato_id": "98765"}), + ): + result = cliente_contratos.get_faturas_by_contrato_id("98765") + assert result is None diff --git a/tests/test_cliente_email/test_cliente_email.py b/tests/test_cliente_email/test_cliente_email.py new file mode 100644 index 00000000..57058f62 --- /dev/null +++ b/tests/test_cliente_email/test_cliente_email.py @@ -0,0 +1,49 @@ +import pytest +import pandas as pd +import logging +from airflow_lappis.plugins.cliente_email import ( + format_csv, +) + +logging.basicConfig(level=logging.INFO) + + +def test_format_csv_success(): + csv_data = """ignore1\nignore2\nignore3\nignore4\nignore5\n1,2,3\n4,5,6\n""" + column_mapping = {0: "A", 1: "B", 2: "C"} + + expected_data = {"A": [1, 4], "B": [2, 5], "C": [3, 6]} + expected_df = pd.DataFrame(expected_data) + + df = format_csv(csv_data, column_mapping) + + pd.testing.assert_frame_equal(df, expected_df) + + +def test_format_csv_invalid_data(): + csv_data = """ignore1\nignore2\nignore3\nignore4\nignore5\na,b,c\nd,e,f\n""" + column_mapping = {0: "X", 1: "Y", 2: "Z"} + + df = format_csv(csv_data, column_mapping) + + assert df.iloc[0]["X"] == "a" + assert df.iloc[1]["Y"] == "e" + + +def test_format_csv_empty_data(): + csv_data = """ignore1\nignore2\nignore3\nignore4\nignore5\n""" + column_mapping = {0: "A", 1: "B"} + + try: + df = format_csv(csv_data, column_mapping) + assert df.empty + except ValueError as e: + assert "No columns to parse from file" in str(e) + + +def test_format_csv_exception(): + csv_data = None # CSV inválido + column_mapping = {0: "A", 1: "B"} + + with pytest.raises(ValueError, match="Erro ao formatar CSV"): + format_csv(csv_data, column_mapping) diff --git a/tests/test_cliente_email/test_extract_csv.py b/tests/test_cliente_email/test_extract_csv.py new file mode 100644 index 00000000..df43da86 --- /dev/null +++ b/tests/test_cliente_email/test_extract_csv.py @@ -0,0 +1,36 @@ +import zipfile +import io +import pandas as pd +from airflow_lappis.plugins.cliente_email import extract_csv_from_zip + + +def test_extract_csv_from_zip_success(): + # Cria um arquivo ZIP em memória com um CSV dentro + csv_data = """ignore1\nignore2\nignore3\nignore4\nignore5\n1,2,3\n4,5,6\n""" + zip_buffer = io.BytesIO() + with zipfile.ZipFile(zip_buffer, "w") as zip_file: + zip_file.writestr("test.csv", csv_data) + zip_buffer.seek(0) + + # Executa a função + column_mapping = {0: "col1", 1: "col2", 2: "col3"} + result = extract_csv_from_zip(zip_buffer.getvalue(), column_mapping) + + # Verifica o resultado + expected = pd.DataFrame({"col1": [1, 4], "col2": [2, 5], "col3": [3, 6]}) + pd.testing.assert_frame_equal(result, expected) + + +def test_extract_csv_from_zip_no_csv(): + # Cria um arquivo ZIP em memória sem CSV + zip_buffer = io.BytesIO() + with zipfile.ZipFile(zip_buffer, "w") as zip_file: + zip_file.writestr("test.txt", "Hello, World!") + zip_buffer.seek(0) + + # Executa a função + column_mapping = {0: "col1", 1: "col2", 2: "col3"} + result = extract_csv_from_zip(zip_buffer.getvalue(), column_mapping) + + # Verifica se o resultado é None + assert result is None diff --git a/tests/test_cliente_email/test_process_email.py b/tests/test_cliente_email/test_process_email.py new file mode 100644 index 00000000..a52245c0 --- /dev/null +++ b/tests/test_cliente_email/test_process_email.py @@ -0,0 +1,54 @@ +from unittest.mock import patch, MagicMock +from airflow_lappis.plugins.cliente_email import process_email_attachments +from airflow_lappis.plugins.cliente_email import fetch_emails +import pandas as pd +from imap_tools import MailMessage + + +def test_fetch_emails_success(): + # Simula a conexão com o servidor IMAP + with patch("airflow_lappis.plugins.cliente_email.MailBox") as mock_mailbox: + # Cria um mock de e-mail com os atributos esperados + mock_email = MagicMock(spec=MailMessage) + mock_email.subject = "Test Subject" + mock_email.from_ = "test@example.com" + + # Configura o mock do MailBox + mock_mailbox_instance = mock_mailbox.return_value + mock_login = mock_mailbox_instance.login.return_value + mock_mailbox_context = mock_login.__enter__.return_value + mock_mailbox_context.fetch.return_value = iter([mock_email]) + + # Executa a função + result = fetch_emails( + imap_server="imap.example.com", + email="user@example.com", + password="password", + sender_email="test@example.com", + subject="Test Subject", + ) + + # Verifica o resultado + assert result is not None + assert result.subject == "Test Subject" + + +def test_process_email_attachments_success(): + # Simula um e-mail com anexo ZIP + mock_email = MagicMock() + mock_email.attachments = [MagicMock(filename="test.zip", payload=b"fake_zip_data")] + + # Simula a extração do CSV do ZIP + with patch( + "airflow_lappis.plugins.cliente_email.extract_csv_from_zip" + ) as mock_extract: + mock_extract.return_value = pd.DataFrame({"col1": [1, 2], "col2": [3, 4]}) + + # Executa a função + result = process_email_attachments( + mock_email, column_mapping={0: "col1", 1: "col2"} + ) + + # Verifica o resultado + assert result is not None + assert not result.empty diff --git a/tests/test_cliente_estrutura/test_get_estrutura.py b/tests/test_cliente_estrutura/test_get_estrutura.py new file mode 100644 index 00000000..8e46e07d --- /dev/null +++ b/tests/test_cliente_estrutura/test_get_estrutura.py @@ -0,0 +1,89 @@ +import pytest +import http +from unittest.mock import patch, Mock +from http import HTTPStatus +from airflow_lappis.plugins.cliente_estrutura import ClienteEstrutura + + +@pytest.fixture +def cliente_estrutura(): + return ClienteEstrutura() + + +def test_get_estrutura_organizacional_resumida_success(cliente_estrutura): + # Configura o mock para retornar uma resposta bem-sucedida + mock_response = Mock() + mock_response.status_code = HTTPStatus.OK + mock_response.json.return_value = {"unidades": [{"id": 1, "nome": "Unidade 1"}]} + + with patch( + "airflow_lappis.plugins.cliente_estrutura.ClienteBase.request", + return_value=(HTTPStatus.OK, mock_response.json.return_value), + ): + # Chama o método get_estrutura_organizacional_resumida + result = cliente_estrutura.get_estrutura_organizacional_resumida() + + # Verifica se o resultado é o esperado + assert result == [{"id": 1, "nome": "Unidade 1"}] + + +def test_get_estrutura_organizacional_resumida_failure(cliente_estrutura): + # Configura o mock para retornar uma resposta de falha + mock_response = Mock() + mock_response.status_code = HTTPStatus.NOT_FOUND + mock_response.json.return_value = None + + with patch( + "airflow_lappis.plugins.cliente_estrutura.ClienteBase.request", + return_value=(HTTPStatus.NOT_FOUND, None), + ): + # Chama o método get_estrutura_organizacional_resumida + result = cliente_estrutura.get_estrutura_organizacional_resumida() + + # Verifica se o resultado é None (falha) + assert result is None + + +def test_get_estrutura_organizacional_resumida_invalid_data(cliente_estrutura): + mock_response = Mock() + mock_response.status_code = HTTPStatus.OK + mock_response.json.return_value = { + "id": 1, + "nome": "Unidade 1", + } # Dados inválidos (não tem a chave "unidades") + + with patch( + "airflow_lappis.plugins.cliente_estrutura.ClienteBase.request", + return_value=(HTTPStatus.OK, mock_response.json.return_value), + ): + # Chama o método get_estrutura_organizacional_resumida + result = cliente_estrutura.get_estrutura_organizacional_resumida() + + # Verifica se o resultado é None (dados inválidos) + assert result is None + + +def test_get_estrutura_organizacional_resumida_with_params(cliente_estrutura): + # Configura o mock para retornar uma resposta bem-sucedida + mock_response = Mock() + mock_response.status_code = HTTPStatus.OK + mock_response.json.return_value = {"unidades": [{"id": 1, "nome": "Unidade 1"}]} + + with patch( + "airflow_lappis.plugins.cliente_estrutura.ClienteBase.request", + return_value=(HTTPStatus.OK, mock_response.json.return_value), + ): + # Chama o método get_estrutura_organizacional_resumida com parâmetros + result = cliente_estrutura.get_estrutura_organizacional_resumida( + codigo_poder="1", codigo_esfera="2", codigo_unidade="3" + ) + + # Verifica se o resultado é o esperado + assert result == [{"id": 1, "nome": "Unidade 1"}] + + # Verifica se os parâmetros foram passados corretamente + cliente_estrutura.request.assert_called_once_with( + http.HTTPMethod.GET, + "/estrutura-organizacional/resumida", + params={"codigoPoder": "1", "codigoEsfera": "2", "codigoUnidade": "3"}, + ) diff --git a/tests/test_cliente_postgres/test_create_table.py b/tests/test_cliente_postgres/test_create_table.py new file mode 100644 index 00000000..afb7abf1 --- /dev/null +++ b/tests/test_cliente_postgres/test_create_table.py @@ -0,0 +1,31 @@ +from unittest.mock import patch, MagicMock +from airflow_lappis.plugins.cliente_postgres import ClientPostgresDB + + +@patch("psycopg2.connect") # Mockando a conexão com o banco +def test_create_table_if_not_exists(mock_connect): + # Criamos um mock para o cursor do banco + mock_conn = MagicMock() + mock_cursor = MagicMock() + mock_connect.return_value.__enter__.return_value = mock_conn + mock_conn.cursor.return_value.__enter__.return_value = mock_cursor + + # Dados fictícios para o teste + sample_data = {"id": 1, "name": "Alice"} + table_name = "users" + primary_key = ["id"] + schema = "public" + + # Criamos a instância da classe + db = ClientPostgresDB("fake_connection_string") + + # Chamamos o método que queremos testar + db.create_table_if_not_exists(sample_data, table_name, primary_key, schema) + + # Verificamos se os métodos corretos foram chamados no banco + mock_cursor.execute.assert_any_call(f"CREATE SCHEMA IF NOT EXISTS {schema};") + create_table_query = ( + f"CREATE TABLE IF NOT EXISTS {schema}.{table_name} (" + f"id TEXT, name TEXT, PRIMARY KEY (id));" + ) + mock_cursor.execute.assert_any_call(create_table_query) diff --git a/tests/test_cliente_postgres/test_drop_table.py b/tests/test_cliente_postgres/test_drop_table.py new file mode 100644 index 00000000..f144fb94 --- /dev/null +++ b/tests/test_cliente_postgres/test_drop_table.py @@ -0,0 +1,71 @@ +from unittest.mock import patch, MagicMock +from airflow_lappis.plugins.cliente_postgres import ClientPostgresDB + + +@patch("airflow_lappis.plugins.cliente_postgres.psycopg2.connect") +def test_drop_table_if_exists(mock_connect): + # Criando uma instância mock do banco de dados + mock_conn = MagicMock() + mock_cursor = MagicMock() + + # Configurando o mock para retornar os objetos corretos + mock_connect.return_value = mock_conn # Simula a conexão + mock_conn.cursor.return_value = mock_cursor # Simula o cursor + + # Criando a instância do ClientPostgresDB com um conn_str fictício + db_client = ClientPostgresDB(conn_str="fake_connection_string") + + # Definindo os parâmetros para o teste + schema = "raw" + table_name = "test_table" + + # Chamando o método a ser testado + db_client.drop_table_if_exists(table_name, schema) + + # Verificando se a query foi executada corretamente + mock_cursor.execute.assert_called_once_with( + f"DROP TABLE IF EXISTS {schema}.{table_name};" + ) + + # Verificando se o commit foi chamado + mock_conn.commit.assert_called_once() + + # Verificando se o cursor e a conexão foram fechados + mock_cursor.close.assert_called_once() + mock_conn.close.assert_called_once() + + +@patch("airflow_lappis.plugins.cliente_postgres.psycopg2.connect") +def test_drop_table_if_exists_error(mock_connect): + # Criando uma instância mock do banco de dados + mock_conn = MagicMock() + mock_cursor = MagicMock() + + # Configurando o mock para simular um erro ao executar a query + mock_connect.return_value = mock_conn # Simula a conexão + mock_conn.cursor.return_value = mock_cursor # Simula o cursor + mock_cursor.execute.side_effect = Exception( + "Erro simulado" + ) # Simula um erro ao executar a query + + # Criando a instância do ClientPostgresDB com um conn_str fictício + db_client = ClientPostgresDB(conn_str="fake_connection_string") + + # Definindo os parâmetros para o teste + schema = "raw" + table_name = "test_table" + + # Chamando o método a ser testado + db_client.drop_table_if_exists(table_name, schema) + + # Verificando se a query foi executada corretamente + mock_cursor.execute.assert_called_once_with( + f"DROP TABLE IF EXISTS {schema}.{table_name};" + ) + + # Verificando se o commit NÃO foi chamado (já que houve um erro) + mock_conn.commit.assert_not_called() + + # Verificando se o cursor e a conexão foram fechados + mock_cursor.close.assert_called_once() + mock_conn.close.assert_called_once() diff --git a/tests/test_cliente_postgres/test_flatten_data.py b/tests/test_cliente_postgres/test_flatten_data.py new file mode 100644 index 00000000..0504d7ea --- /dev/null +++ b/tests/test_cliente_postgres/test_flatten_data.py @@ -0,0 +1,43 @@ +from airflow_lappis.plugins.cliente_postgres import ClientPostgresDB + + +def test_flatten_data(): + db = ClientPostgresDB( + "fake_connection_string" + ) # Apenas passamos um valor qualquer para conn_str + + nested_data = [ + { + "id": 1, + "name": "Alice", + "address": {"city": "New York", "zip": "10001"}, + "emails": ["alice@example.com", "alice.work@example.com"], + }, + { + "id": 2, + "name": "Bob", + "address": {"city": "Los Angeles", "zip": "90001"}, + "emails": ["bob@example.com"], + }, + ] + + expected_output = [ + { + "id": 1, + "name": "Alice", + "address__city": "New York", + "address__zip": "10001", + "emails": "['alice@example.com', 'alice.work@example.com']", + }, + { + "id": 2, + "name": "Bob", + "address__city": "Los Angeles", + "address__zip": "90001", + "emails": "['bob@example.com']", + }, + ] + + result = db._flatten_data(nested_data) + + assert result == expected_output diff --git a/tests/test_cliente_postgres/test_get_contratos_ids.py b/tests/test_cliente_postgres/test_get_contratos_ids.py new file mode 100644 index 00000000..858e8309 --- /dev/null +++ b/tests/test_cliente_postgres/test_get_contratos_ids.py @@ -0,0 +1,28 @@ +from unittest.mock import patch, MagicMock +from airflow_lappis.plugins.cliente_postgres import ClientPostgresDB + + +@patch("airflow_lappis.plugins.cliente_postgres.psycopg2.connect") +def test_get_contratos_ids(mock_connect): + # Criando uma instância mock do banco de dados + mock_conn = MagicMock() + mock_cursor = MagicMock() + + # Configurando o mock para retornar os objetos corretos + mock_connect.return_value.__enter__.return_value = mock_conn + mock_conn.cursor.return_value.__enter__.return_value = mock_cursor + + # Definindo valores simulados que o banco retornaria + mock_cursor.fetchall.return_value = [(1,), (2,), (3,)] + + # Criando a instância do ClientPostgresDB com um conn_str fictício + db_client = ClientPostgresDB(conn_str="fake_connection_string") + + # Chamando o método a ser testado + contratos_ids = db_client.get_contratos_ids() + + # Verificando se a query foi executada corretamente + mock_cursor.execute.assert_called_once_with("SELECT id FROM raw.contratos") + + # Verificando se o retorno do método está correto + assert contratos_ids == [1, 2, 3] diff --git a/tests/test_cliente_postgres/test_insert_csv_data.py b/tests/test_cliente_postgres/test_insert_csv_data.py new file mode 100644 index 00000000..bbe8e1f4 --- /dev/null +++ b/tests/test_cliente_postgres/test_insert_csv_data.py @@ -0,0 +1,42 @@ +from unittest.mock import patch, MagicMock +from airflow_lappis.plugins.cliente_postgres import ClientPostgresDB + + +@patch("airflow_lappis.plugins.cliente_postgres.psycopg2.connect") +def test_insert_csv_data(mock_connect): + # Criando uma instância mock do banco de dados + mock_conn = MagicMock() + mock_cursor = MagicMock() + + # Configurando o mock para retornar os objetos corretos + mock_connect.return_value.__enter__.return_value = mock_conn + mock_conn.cursor.return_value.__enter__.return_value = mock_cursor + + # Configurando encoding da conexão para evitar KeyError + mock_conn.encoding = "UTF8" + mock_cursor.connection.encoding = "UTF8" + + # Criando a instância do ClientPostgresDB com um conn_str fictício + db_client = ClientPostgresDB(conn_str="fake_connection_string") + + # Dados de entrada para o teste + csv_data = "id,name\n1,John Doe\n2,Jane Doe" + table_name = "users" + schema = "public" + + # Chamando o método a ser testado + with ( + patch.object(db_client, "drop_table_if_exists") as mock_drop_table_if_exists, + patch.object(db_client, "insert_data") as mock_insert_data, + ): + + db_client.insert_csv_data(csv_data, table_name, schema) + + # Verificando se drop_table_if_exists foi chamado corretamente + mock_drop_table_if_exists.assert_called_once_with(table_name, schema) + + # Verificando se insert_data foi chamado corretamente + expected_data = [{"id": 1, "name": "John Doe"}, {"id": 2, "name": "Jane Doe"}] + mock_insert_data.assert_called_once_with( + expected_data, table_name, primary_key=None, schema=schema + ) diff --git a/tests/test_cliente_postgres/test_insert_data.py b/tests/test_cliente_postgres/test_insert_data.py new file mode 100644 index 00000000..d73d6e5e --- /dev/null +++ b/tests/test_cliente_postgres/test_insert_data.py @@ -0,0 +1,45 @@ +from unittest.mock import patch, MagicMock +from airflow_lappis.plugins.cliente_postgres import ClientPostgresDB + + +@patch("airflow_lappis.plugins.cliente_postgres.psycopg2.connect") +def test_insert_data(mock_connect): + # Criando uma instância mock do banco de dados + mock_conn = MagicMock() + mock_cursor = MagicMock() + + # Configurando o mock para retornar os objetos corretos + mock_connect.return_value.__enter__.return_value = mock_conn + mock_conn.cursor.return_value.__enter__.return_value = mock_cursor + + # Configurando encoding da conexão para evitar KeyError + mock_conn.encoding = "UTF8" + mock_cursor.connection.encoding = "UTF8" + + # Criando a instância do ClientPostgresDB com um conn_str fictício + db_client = ClientPostgresDB(conn_str="fake_connection_string") + + # Dados de entrada para o teste + data = [{"id": 1, "name": "John Doe"}, {"id": 2, "name": "Jane Doe"}] + table_name = "users" + schema = "public" + conflict_fields = ["id"] + primary_key = ["id"] + + # Chamando o método a ser testado + with patch("psycopg2.extras.execute_values") as mock_execute_values: + db_client.insert_data(data, table_name, conflict_fields, primary_key, schema) + + # Verificando se a query foi construída corretamente + expected_sql = ( + f"INSERT INTO {schema}.{table_name} (id, name) VALUES %s " + "ON CONFLICT (id) DO UPDATE SET id = EXCLUDED.id, name = EXCLUDED.name" + ) + + # Verificando se execute_values foi chamado corretamente + mock_execute_values.assert_called_once_with( + mock_cursor, expected_sql, [(1, "John Doe"), (2, "Jane Doe")] + ) + + # Garantir que commit foi chamado + mock_conn.commit.assert_called_once() From 8cf35b86c5af10038c8b4813866baf73c3e51ccf Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Fri, 28 Feb 2025 19:31:51 +0000 Subject: [PATCH 018/317] feat(dag): adiciona dag de ingestao dos beneficiarios --- .../programa_beneficiario_ingest_dag.py | 54 +++++++++++++++++++ .../dags/data_ingest/programas_ingest_dag.py | 46 ++++++++++++++++ airflow_lappis/plugins/cliente_postgres.py | 10 ++++ airflow_lappis/plugins/cliente_ted.py | 53 ++++++++++++++++++ 4 files changed, 163 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/programa_beneficiario_ingest_dag.py create mode 100644 airflow_lappis/dags/data_ingest/programas_ingest_dag.py create mode 100644 airflow_lappis/plugins/cliente_ted.py diff --git a/airflow_lappis/dags/data_ingest/programa_beneficiario_ingest_dag.py b/airflow_lappis/dags/data_ingest/programa_beneficiario_ingest_dag.py new file mode 100644 index 00000000..168ff914 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/programa_beneficiario_ingest_dag.py @@ -0,0 +1,54 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from postgres_helpers import get_postgres_conn +from cliente_ted import ClienteTed +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Davi", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["ted_api", "programa_beneficiario"], +) +def api_programa_beneficiario_dag() -> None: + + @task + def fetch_and_store_programa_beneficiario() -> None: + logging.info("Starting api_programa_beneficiario_dag DAG") + api = ClienteTed() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + tx_codigo_siorgs = "7" + + beneficiario = api.get_ted_by_programa_beneficiario(tx_codigo_siorgs) + if beneficiario: + logging.info( + f"Tipo de beneficiario: {type(beneficiario)}, Conteúdo: {beneficiario}" + ) + logging.info("Inserting beneficiario into PostgreSQL") + unique_id_programas = [ + {"id_programa": id_prog} + for id_prog in {b["id_programa"] for b in beneficiario} + ] + + db.insert_data( + unique_id_programas, + "beneficiario", + schema="ted", + ) + else: + logging.warning( + f"No beneficiario found for tx_codigo_siorg: {tx_codigo_siorgs}" + ) + + fetch_and_store_programa_beneficiario() + + +dag_instace = api_programa_beneficiario_dag() diff --git a/airflow_lappis/dags/data_ingest/programas_ingest_dag.py b/airflow_lappis/dags/data_ingest/programas_ingest_dag.py new file mode 100644 index 00000000..4a9c203d --- /dev/null +++ b/airflow_lappis/dags/data_ingest/programas_ingest_dag.py @@ -0,0 +1,46 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from postgres_helpers import get_postgres_conn +from cliente_ted import ClienteTed +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Davi", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["ted_api", "programas"], +) +def api_programas_dag() -> None: + + @task + def fetch_and_store_programas() -> None: + logging.info("Starting api_programas_dag DAG") + api = ClienteTed() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + id_programas = db.get_id_programas() + + for id_programa in id_programas: + programas = api.get_programa_by_id_programa(id_programa) + if programas: + logging.info("Inserting programas into PostgreSQL") + + db.insert_data( + programas, + "programas", + schema="ted", + ) + else: + logging.warning(f"No programas found for id_programas: {id_programas}") + + fetch_and_store_programas() + + +dag_instance = api_programas_dag() diff --git a/airflow_lappis/plugins/cliente_postgres.py b/airflow_lappis/plugins/cliente_postgres.py index eb4f77fa..7806d977 100755 --- a/airflow_lappis/plugins/cliente_postgres.py +++ b/airflow_lappis/plugins/cliente_postgres.py @@ -190,6 +190,16 @@ def get_contratos_ids(self) -> List[int]: contratos_ids = [row[0] for row in cursor.fetchall()] return contratos_ids + def get_id_programas(self) -> List[int]: + """Extrai todos os IDs de programas da tabela beneficiário.""" + query = "SELECT id_programa FROM ted.beneficiario" + + with psycopg2.connect(self.conn_str) as conn: + with conn.cursor() as cursor: + cursor.execute(query) + id_programas = [row[0] for row in cursor.fetchall()] + return id_programas + def drop_table_if_exists(self, table_name: str, schema: str = "raw") -> None: """Remove a tabela se ela existir.""" conn = psycopg2.connect(self.conn_str) diff --git a/airflow_lappis/plugins/cliente_ted.py b/airflow_lappis/plugins/cliente_ted.py new file mode 100644 index 00000000..e00678ca --- /dev/null +++ b/airflow_lappis/plugins/cliente_ted.py @@ -0,0 +1,53 @@ +import http +import logging +from cliente_base import ClienteBase + + +class ClienteTed(ClienteBase): + BASE_URL = "https://api.transferegov.gestao.gov.br/ted/" + BASE_HEADER = {"accept": "application/json"} + + def __init__(self) -> None: + super().__init__(base_url=ClienteTed.BASE_URL) + + def get_ted_by_programa_beneficiario(self, tx_codigo_siorg: str) -> list | None: + + endpoint = f"programa_beneficiario?tx_codigo_siorg=eq.{tx_codigo_siorg}" + logging.info( + f"[cliente_ted.py] Fetching ted for programa beneficiario: {tx_codigo_siorg}" + ) + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER + ) + if status == http.HTTPStatus.OK and isinstance(data, list): + logging.info( + "[cliente_ted.py] Successfully fetched ted for programa beneficiario: " + f"{tx_codigo_siorg}" + ) + return data + else: + logging.warning( + "[cliente_ted.py] Failed to fetch ted for programa beneficiario: " + f"{tx_codigo_siorg} with status: {status}" + ) + return None + + def get_programa_by_id_programa(self, id_programa: str) -> list | None: + + endpoint = f"programa?id_programa=eq.{id_programa}" + logging.info(f"[cliente_ted.py] Fetching programa for id_programa: {id_programa}") + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER + ) + if status == http.HTTPStatus.OK and isinstance(data, list): + logging.info( + "[cliente_ted.py] Successfully fetched programa for id_programa: " + f"{id_programa}" + ) + return data + else: + logging.warning( + "[cliente_ted.py] Failed to fetch programa for id_programa: " + f"{id_programa} with status: {status}" + ) + return None From c970f9bad091d3c19daf378c4145b5a5f1547d41 Mon Sep 17 00:00:00 2001 From: arthrok Date: Fri, 28 Feb 2025 17:25:54 -0300 Subject: [PATCH 019/317] Revert "Merge branch 'feat/testes-unitarios' into 'main'" This reverts commit 7df46ef31db0499a558e8f9380fa1d8135ff6a48, reversing changes made to 2ce00d533d36048a9ca2f0a5a171af18f2134900. --- Makefile | 2 +- airflow_lappis/plugins/cliente_base.py | 2 +- airflow_lappis/plugins/cliente_email.py | 1 + airflow_lappis/plugins/cliente_estrutura.py | 4 +- airflow_lappis/plugins/cliente_postgres.py | 1 - tests/conftest.py | 7 -- tests/test_cliente_base/test_request.py | 40 --------- .../test_get_contratos.py | 62 ------------- .../test_get_contratos_inativos.py | 62 ------------- .../test_get_cronograma.py | 49 ---------- .../test_get_empenhos.py | 49 ---------- .../test_get_faturas.py | 49 ---------- .../test_cliente_email/test_cliente_email.py | 49 ---------- tests/test_cliente_email/test_extract_csv.py | 36 -------- .../test_cliente_email/test_process_email.py | 54 ----------- .../test_get_estrutura.py | 89 ------------------- .../test_create_table.py | 31 ------- .../test_cliente_postgres/test_drop_table.py | 71 --------------- .../test_flatten_data.py | 43 --------- .../test_get_contratos_ids.py | 28 ------ .../test_insert_csv_data.py | 42 --------- .../test_cliente_postgres/test_insert_data.py | 45 ---------- 22 files changed, 5 insertions(+), 811 deletions(-) delete mode 100644 tests/conftest.py delete mode 100644 tests/test_cliente_base/test_request.py delete mode 100644 tests/test_cliente_contratos/test_get_contratos.py delete mode 100644 tests/test_cliente_contratos/test_get_contratos_inativos.py delete mode 100644 tests/test_cliente_contratos/test_get_cronograma.py delete mode 100644 tests/test_cliente_contratos/test_get_empenhos.py delete mode 100644 tests/test_cliente_contratos/test_get_faturas.py delete mode 100644 tests/test_cliente_email/test_cliente_email.py delete mode 100644 tests/test_cliente_email/test_extract_csv.py delete mode 100644 tests/test_cliente_email/test_process_email.py delete mode 100644 tests/test_cliente_estrutura/test_get_estrutura.py delete mode 100644 tests/test_cliente_postgres/test_create_table.py delete mode 100644 tests/test_cliente_postgres/test_drop_table.py delete mode 100644 tests/test_cliente_postgres/test_flatten_data.py delete mode 100644 tests/test_cliente_postgres/test_get_contratos_ids.py delete mode 100644 tests/test_cliente_postgres/test_insert_csv_data.py delete mode 100644 tests/test_cliente_postgres/test_insert_data.py diff --git a/Makefile b/Makefile index 71bd4813..58b5b6a6 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ format: lint: poetry run black . --check poetry run ruff check . - poetry run mypy airflow_lappis --explicit-package-bases --install-types + poetry run mypy . --explicit-package-bases --install-types poetry run sqlfmt ./airflow_lappis/dags/dbt --check [ "${GITLAB_CI}" ] || poetry run sqlfluff lint ./airflow_lappis/dags/dbt diff --git a/airflow_lappis/plugins/cliente_base.py b/airflow_lappis/plugins/cliente_base.py index 6a293897..3f8e6269 100755 --- a/airflow_lappis/plugins/cliente_base.py +++ b/airflow_lappis/plugins/cliente_base.py @@ -52,7 +52,7 @@ def request( f"[cliente_base.py] API failed with status {status} on " f"attempt {attempt + 1}. Error: {str(e)}" ) - if attempt < self.DEFAULT_MAX_RETRIES - 1: + if attempt < self.DEFAULT_MAX_RETRIES: time.sleep(attempt**2 * self.DEFAULT_SLEEP_SECONDS) else: logging.error( diff --git a/airflow_lappis/plugins/cliente_email.py b/airflow_lappis/plugins/cliente_email.py index af97f449..0f079c12 100755 --- a/airflow_lappis/plugins/cliente_email.py +++ b/airflow_lappis/plugins/cliente_email.py @@ -3,6 +3,7 @@ import zipfile from typing import Optional from typing_extensions import Buffer + import pandas as pd from imap_tools import MailBox, AND, MailMessage import chardet diff --git a/airflow_lappis/plugins/cliente_estrutura.py b/airflow_lappis/plugins/cliente_estrutura.py index e2a61ef4..7522b657 100755 --- a/airflow_lappis/plugins/cliente_estrutura.py +++ b/airflow_lappis/plugins/cliente_estrutura.py @@ -39,7 +39,7 @@ def get_estrutura_organizacional_resumida( status, data = self.request(http.HTTPMethod.GET, endpoint, params=params) return ( - data.get("unidades") - if status == http.HTTPStatus.OK and type(data) is dict and "unidades" in data + data.get("unidades", []) + if status == http.HTTPStatus.OK and type(data) is dict else None ) diff --git a/airflow_lappis/plugins/cliente_postgres.py b/airflow_lappis/plugins/cliente_postgres.py index 7806d977..3f9edb5d 100755 --- a/airflow_lappis/plugins/cliente_postgres.py +++ b/airflow_lappis/plugins/cliente_postgres.py @@ -4,7 +4,6 @@ from pandas import json_normalize import pandas as pd import io -import psycopg2.extras class ClientPostgresDB: diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index b36e5a36..00000000 --- a/tests/conftest.py +++ /dev/null @@ -1,7 +0,0 @@ -import sys -import os - -sys.path.insert( - 0, - os.path.abspath(os.path.join(os.path.dirname(__file__), "../airflow_lappis/plugins")), -) diff --git a/tests/test_cliente_base/test_request.py b/tests/test_cliente_base/test_request.py deleted file mode 100644 index 6855c63a..00000000 --- a/tests/test_cliente_base/test_request.py +++ /dev/null @@ -1,40 +0,0 @@ -import pytest -from unittest.mock import patch, Mock -from http import HTTPStatus -import httpx -from airflow_lappis.plugins.cliente_base import ClienteBase - - -@pytest.fixture -def cliente_base(): - return ClienteBase(base_url="http://example.com") - - -def test_request_success(cliente_base): - # Configura o mock para retornar uma resposta bem-sucedida - mock_response = Mock() - mock_response.status_code = HTTPStatus.OK - mock_response.json.return_value = {"key": "value"} - mock_response.raise_for_status.return_value = None - - with patch("httpx.Client.request", return_value=mock_response): - # Chama o método request - status, response = cliente_base.request("GET", "/test") - - # Verifica se o status e a resposta são os esperados - assert status == HTTPStatus.OK - assert response == {"key": "value"} - - -def test_request_failure(cliente_base): - # Configura o mock para simular uma exceção HTTPError - mock_response = Mock() - mock_response.status_code = HTTPStatus.BAD_REQUEST - mock_response.raise_for_status.side_effect = httpx.HTTPError("Bad Request") - - with patch("httpx.Client.request", return_value=mock_response): - # Chama o método request e verifica se a exceção é levantada após as tentativas - with pytest.raises( - Exception, match="API failed after the maximum number of attempts!" - ): - cliente_base.request("GET", "/test") diff --git a/tests/test_cliente_contratos/test_get_contratos.py b/tests/test_cliente_contratos/test_get_contratos.py deleted file mode 100644 index 85947298..00000000 --- a/tests/test_cliente_contratos/test_get_contratos.py +++ /dev/null @@ -1,62 +0,0 @@ -import pytest -from unittest.mock import patch, Mock -from http import HTTPStatus -from airflow_lappis.plugins.cliente_contratos import ClienteContratos - - -@pytest.fixture -def cliente_contratos(): - return ClienteContratos() - - -def test_get_contratos_by_ug_success(cliente_contratos): - # Configura o mock para retornar uma resposta bem-sucedida - mock_response = Mock() - mock_response.status_code = HTTPStatus.OK - mock_response.json.return_value = [{"id": 1, "ug_code": "12345"}] - - with patch( - "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", - return_value=(HTTPStatus.OK, mock_response.json.return_value), - ): - # Chama o método get_contratos_by_ug - result = cliente_contratos.get_contratos_by_ug("12345") - - # Verifica se o resultado é o esperado - assert result == [{"id": 1, "ug_code": "12345"}] - - -def test_get_contratos_by_ug_failure(cliente_contratos): - # Configura o mock para retornar uma resposta de falha - mock_response = Mock() - mock_response.status_code = HTTPStatus.NOT_FOUND - mock_response.json.return_value = None - - with patch( - "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", - return_value=(HTTPStatus.NOT_FOUND, None), - ): - # Chama o método get_contratos_by_ug - result = cliente_contratos.get_contratos_by_ug("12345") - - # Verifica se o resultado é None (falha) - assert result is None - - -def test_get_contratos_by_ug_invalid_data(cliente_contratos): - mock_response = Mock() - mock_response.status_code = HTTPStatus.OK - mock_response.json.return_value = { - "id": 1, - "ug_code": "12345", - } # Dados inválidos (não é uma lista) - - with patch( - "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", - return_value=(HTTPStatus.OK, mock_response.json.return_value), - ): - # Chama o método get_contratos_by_ug - result = cliente_contratos.get_contratos_by_ug("12345") - - # Verifica se o resultado é None (dados inválidos) - assert result is None diff --git a/tests/test_cliente_contratos/test_get_contratos_inativos.py b/tests/test_cliente_contratos/test_get_contratos_inativos.py deleted file mode 100644 index 04adf66c..00000000 --- a/tests/test_cliente_contratos/test_get_contratos_inativos.py +++ /dev/null @@ -1,62 +0,0 @@ -import pytest -from unittest.mock import patch, Mock -from http import HTTPStatus -from airflow_lappis.plugins.cliente_contratos import ClienteContratos - - -@pytest.fixture -def cliente_contratos(): - return ClienteContratos() - - -def test_get_contratos_inativos_by_ug_success(cliente_contratos): - # Configura o mock para retornar uma resposta bem-sucedida - mock_response = Mock() - mock_response.status_code = HTTPStatus.OK - mock_response.json.return_value = [{"id": 1, "ug_code": "12345", "status": "inativo"}] - - with patch( - "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", - return_value=(HTTPStatus.OK, mock_response.json.return_value), - ): - # Chama o método get_contratos_inativos_by_ug - result = cliente_contratos.get_contratos_inativos_by_ug("12345") - - # Verifica se o resultado é o esperado - assert result == [{"id": 1, "ug_code": "12345", "status": "inativo"}] - - -def test_get_contratos_inativos_by_ug_failure(cliente_contratos): - # Configura o mock para retornar uma resposta de falha - mock_response = Mock() - mock_response.status_code = HTTPStatus.NOT_FOUND - mock_response.json.return_value = None - - with patch( - "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", - return_value=(HTTPStatus.NOT_FOUND, None), - ): - # Chama o método get_contratos_inativos_by_ug - result = cliente_contratos.get_contratos_inativos_by_ug("12345") - - # Verifica se o resultado é None (falha) - assert result is None - - -def test_get_contratos_inativos_by_ug_invalid_data(cliente_contratos): - mock_response = Mock() - mock_response.status_code = HTTPStatus.OK - mock_response.json.return_value = { - "id": 1, - "ug_code": "12345", - } # Dados inválidos (não é uma lista) - - with patch( - "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", - return_value=(HTTPStatus.OK, mock_response.json.return_value), - ): - # Chama o método get_contratos_inativos_by_ug - result = cliente_contratos.get_contratos_inativos_by_ug("12345") - - # Verifica se o resultado é None (dados inválidos) - assert result is None diff --git a/tests/test_cliente_contratos/test_get_cronograma.py b/tests/test_cliente_contratos/test_get_cronograma.py deleted file mode 100644 index 806a43f3..00000000 --- a/tests/test_cliente_contratos/test_get_cronograma.py +++ /dev/null @@ -1,49 +0,0 @@ -import pytest -from unittest.mock import patch, Mock -from http import HTTPStatus -from airflow_lappis.plugins.cliente_contratos import ClienteContratos - - -@pytest.fixture -def cliente_contratos(): - return ClienteContratos() - - -def test_get_cronograma_by_contrato_id_success(cliente_contratos): - # Mockando a resposta esperada - mock_response = Mock() - mock_response.status_code = HTTPStatus.OK - mock_response.json.return_value = [ - {"id": 1, "contrato_id": "98765", "etapa": "Fundação"}, - {"id": 2, "contrato_id": "98765", "etapa": "Estrutura"}, - ] - - with patch( - "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", - return_value=(HTTPStatus.OK, mock_response.json.return_value), - ): - result = cliente_contratos.get_cronograma_by_contrato_id("98765") - assert result == [ - {"id": 1, "contrato_id": "98765", "etapa": "Fundação"}, - {"id": 2, "contrato_id": "98765", "etapa": "Estrutura"}, - ] - - -def test_get_cronograma_by_contrato_id_failure(cliente_contratos): - # Mockando uma resposta de falha - with patch( - "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", - return_value=(HTTPStatus.NOT_FOUND, None), - ): - result = cliente_contratos.get_cronograma_by_contrato_id("98765") - assert result is None - - -def test_get_cronograma_by_contrato_id_invalid_data(cliente_contratos): - # Mockando uma resposta com dados inválidos (não é uma lista) - with patch( - "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", - return_value=(HTTPStatus.OK, {"id": 1, "contrato_id": "98765"}), - ): - result = cliente_contratos.get_cronograma_by_contrato_id("98765") - assert result is None diff --git a/tests/test_cliente_contratos/test_get_empenhos.py b/tests/test_cliente_contratos/test_get_empenhos.py deleted file mode 100644 index 151e2eb9..00000000 --- a/tests/test_cliente_contratos/test_get_empenhos.py +++ /dev/null @@ -1,49 +0,0 @@ -import pytest -from unittest.mock import patch, Mock -from http import HTTPStatus -from airflow_lappis.plugins.cliente_contratos import ClienteContratos - - -@pytest.fixture -def cliente_contratos(): - return ClienteContratos() - - -def test_get_empenhos_by_contrato_id_success(cliente_contratos): - # Mockando a resposta esperada - mock_response = Mock() - mock_response.status_code = HTTPStatus.OK - mock_response.json.return_value = [ - {"id": 1, "contrato_id": "98765", "valor": 500.0}, - {"id": 2, "contrato_id": "98765", "valor": 750.0}, - ] - - with patch( - "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", - return_value=(HTTPStatus.OK, mock_response.json.return_value), - ): - result = cliente_contratos.get_empenhos_by_contrato_id("98765") - assert result == [ - {"id": 1, "contrato_id": "98765", "valor": 500.0}, - {"id": 2, "contrato_id": "98765", "valor": 750.0}, - ] - - -def test_get_empenhos_by_contrato_id_failure(cliente_contratos): - # Mockando uma resposta de falha - with patch( - "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", - return_value=(HTTPStatus.NOT_FOUND, None), - ): - result = cliente_contratos.get_empenhos_by_contrato_id("98765") - assert result is None - - -def test_get_empenhos_by_contrato_id_invalid_data(cliente_contratos): - # Mockando uma resposta com dados inválidos (não é uma lista) - with patch( - "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", - return_value=(HTTPStatus.OK, {"id": 1, "contrato_id": "98765"}), - ): - result = cliente_contratos.get_empenhos_by_contrato_id("98765") - assert result is None diff --git a/tests/test_cliente_contratos/test_get_faturas.py b/tests/test_cliente_contratos/test_get_faturas.py deleted file mode 100644 index e9ad88b4..00000000 --- a/tests/test_cliente_contratos/test_get_faturas.py +++ /dev/null @@ -1,49 +0,0 @@ -import pytest -from unittest.mock import patch, Mock -from http import HTTPStatus -from airflow_lappis.plugins.cliente_contratos import ClienteContratos - - -@pytest.fixture -def cliente_contratos(): - return ClienteContratos() - - -def test_get_faturas_by_contrato_id_success(cliente_contratos): - # Mockando a resposta esperada - mock_response = Mock() - mock_response.status_code = HTTPStatus.OK - mock_response.json.return_value = [ - {"id": 1, "contrato_id": "98765", "valor": 100.0}, - {"id": 2, "contrato_id": "98765", "valor": 200.0}, - ] - - with patch( - "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", - return_value=(HTTPStatus.OK, mock_response.json.return_value), - ): - result = cliente_contratos.get_faturas_by_contrato_id("98765") - assert result == [ - {"id": 1, "contrato_id": "98765", "valor": 100.0}, - {"id": 2, "contrato_id": "98765", "valor": 200.0}, - ] - - -def test_get_faturas_by_contrato_id_failure(cliente_contratos): - # Mockando uma resposta de falha - with patch( - "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", - return_value=(HTTPStatus.NOT_FOUND, None), - ): - result = cliente_contratos.get_faturas_by_contrato_id("98765") - assert result is None - - -def test_get_faturas_by_contrato_id_invalid_data(cliente_contratos): - # Mockando uma resposta com dados inválidos (não é uma lista) - with patch( - "airflow_lappis.plugins.cliente_contratos.ClienteBase.request", - return_value=(HTTPStatus.OK, {"id": 1, "contrato_id": "98765"}), - ): - result = cliente_contratos.get_faturas_by_contrato_id("98765") - assert result is None diff --git a/tests/test_cliente_email/test_cliente_email.py b/tests/test_cliente_email/test_cliente_email.py deleted file mode 100644 index 57058f62..00000000 --- a/tests/test_cliente_email/test_cliente_email.py +++ /dev/null @@ -1,49 +0,0 @@ -import pytest -import pandas as pd -import logging -from airflow_lappis.plugins.cliente_email import ( - format_csv, -) - -logging.basicConfig(level=logging.INFO) - - -def test_format_csv_success(): - csv_data = """ignore1\nignore2\nignore3\nignore4\nignore5\n1,2,3\n4,5,6\n""" - column_mapping = {0: "A", 1: "B", 2: "C"} - - expected_data = {"A": [1, 4], "B": [2, 5], "C": [3, 6]} - expected_df = pd.DataFrame(expected_data) - - df = format_csv(csv_data, column_mapping) - - pd.testing.assert_frame_equal(df, expected_df) - - -def test_format_csv_invalid_data(): - csv_data = """ignore1\nignore2\nignore3\nignore4\nignore5\na,b,c\nd,e,f\n""" - column_mapping = {0: "X", 1: "Y", 2: "Z"} - - df = format_csv(csv_data, column_mapping) - - assert df.iloc[0]["X"] == "a" - assert df.iloc[1]["Y"] == "e" - - -def test_format_csv_empty_data(): - csv_data = """ignore1\nignore2\nignore3\nignore4\nignore5\n""" - column_mapping = {0: "A", 1: "B"} - - try: - df = format_csv(csv_data, column_mapping) - assert df.empty - except ValueError as e: - assert "No columns to parse from file" in str(e) - - -def test_format_csv_exception(): - csv_data = None # CSV inválido - column_mapping = {0: "A", 1: "B"} - - with pytest.raises(ValueError, match="Erro ao formatar CSV"): - format_csv(csv_data, column_mapping) diff --git a/tests/test_cliente_email/test_extract_csv.py b/tests/test_cliente_email/test_extract_csv.py deleted file mode 100644 index df43da86..00000000 --- a/tests/test_cliente_email/test_extract_csv.py +++ /dev/null @@ -1,36 +0,0 @@ -import zipfile -import io -import pandas as pd -from airflow_lappis.plugins.cliente_email import extract_csv_from_zip - - -def test_extract_csv_from_zip_success(): - # Cria um arquivo ZIP em memória com um CSV dentro - csv_data = """ignore1\nignore2\nignore3\nignore4\nignore5\n1,2,3\n4,5,6\n""" - zip_buffer = io.BytesIO() - with zipfile.ZipFile(zip_buffer, "w") as zip_file: - zip_file.writestr("test.csv", csv_data) - zip_buffer.seek(0) - - # Executa a função - column_mapping = {0: "col1", 1: "col2", 2: "col3"} - result = extract_csv_from_zip(zip_buffer.getvalue(), column_mapping) - - # Verifica o resultado - expected = pd.DataFrame({"col1": [1, 4], "col2": [2, 5], "col3": [3, 6]}) - pd.testing.assert_frame_equal(result, expected) - - -def test_extract_csv_from_zip_no_csv(): - # Cria um arquivo ZIP em memória sem CSV - zip_buffer = io.BytesIO() - with zipfile.ZipFile(zip_buffer, "w") as zip_file: - zip_file.writestr("test.txt", "Hello, World!") - zip_buffer.seek(0) - - # Executa a função - column_mapping = {0: "col1", 1: "col2", 2: "col3"} - result = extract_csv_from_zip(zip_buffer.getvalue(), column_mapping) - - # Verifica se o resultado é None - assert result is None diff --git a/tests/test_cliente_email/test_process_email.py b/tests/test_cliente_email/test_process_email.py deleted file mode 100644 index a52245c0..00000000 --- a/tests/test_cliente_email/test_process_email.py +++ /dev/null @@ -1,54 +0,0 @@ -from unittest.mock import patch, MagicMock -from airflow_lappis.plugins.cliente_email import process_email_attachments -from airflow_lappis.plugins.cliente_email import fetch_emails -import pandas as pd -from imap_tools import MailMessage - - -def test_fetch_emails_success(): - # Simula a conexão com o servidor IMAP - with patch("airflow_lappis.plugins.cliente_email.MailBox") as mock_mailbox: - # Cria um mock de e-mail com os atributos esperados - mock_email = MagicMock(spec=MailMessage) - mock_email.subject = "Test Subject" - mock_email.from_ = "test@example.com" - - # Configura o mock do MailBox - mock_mailbox_instance = mock_mailbox.return_value - mock_login = mock_mailbox_instance.login.return_value - mock_mailbox_context = mock_login.__enter__.return_value - mock_mailbox_context.fetch.return_value = iter([mock_email]) - - # Executa a função - result = fetch_emails( - imap_server="imap.example.com", - email="user@example.com", - password="password", - sender_email="test@example.com", - subject="Test Subject", - ) - - # Verifica o resultado - assert result is not None - assert result.subject == "Test Subject" - - -def test_process_email_attachments_success(): - # Simula um e-mail com anexo ZIP - mock_email = MagicMock() - mock_email.attachments = [MagicMock(filename="test.zip", payload=b"fake_zip_data")] - - # Simula a extração do CSV do ZIP - with patch( - "airflow_lappis.plugins.cliente_email.extract_csv_from_zip" - ) as mock_extract: - mock_extract.return_value = pd.DataFrame({"col1": [1, 2], "col2": [3, 4]}) - - # Executa a função - result = process_email_attachments( - mock_email, column_mapping={0: "col1", 1: "col2"} - ) - - # Verifica o resultado - assert result is not None - assert not result.empty diff --git a/tests/test_cliente_estrutura/test_get_estrutura.py b/tests/test_cliente_estrutura/test_get_estrutura.py deleted file mode 100644 index 8e46e07d..00000000 --- a/tests/test_cliente_estrutura/test_get_estrutura.py +++ /dev/null @@ -1,89 +0,0 @@ -import pytest -import http -from unittest.mock import patch, Mock -from http import HTTPStatus -from airflow_lappis.plugins.cliente_estrutura import ClienteEstrutura - - -@pytest.fixture -def cliente_estrutura(): - return ClienteEstrutura() - - -def test_get_estrutura_organizacional_resumida_success(cliente_estrutura): - # Configura o mock para retornar uma resposta bem-sucedida - mock_response = Mock() - mock_response.status_code = HTTPStatus.OK - mock_response.json.return_value = {"unidades": [{"id": 1, "nome": "Unidade 1"}]} - - with patch( - "airflow_lappis.plugins.cliente_estrutura.ClienteBase.request", - return_value=(HTTPStatus.OK, mock_response.json.return_value), - ): - # Chama o método get_estrutura_organizacional_resumida - result = cliente_estrutura.get_estrutura_organizacional_resumida() - - # Verifica se o resultado é o esperado - assert result == [{"id": 1, "nome": "Unidade 1"}] - - -def test_get_estrutura_organizacional_resumida_failure(cliente_estrutura): - # Configura o mock para retornar uma resposta de falha - mock_response = Mock() - mock_response.status_code = HTTPStatus.NOT_FOUND - mock_response.json.return_value = None - - with patch( - "airflow_lappis.plugins.cliente_estrutura.ClienteBase.request", - return_value=(HTTPStatus.NOT_FOUND, None), - ): - # Chama o método get_estrutura_organizacional_resumida - result = cliente_estrutura.get_estrutura_organizacional_resumida() - - # Verifica se o resultado é None (falha) - assert result is None - - -def test_get_estrutura_organizacional_resumida_invalid_data(cliente_estrutura): - mock_response = Mock() - mock_response.status_code = HTTPStatus.OK - mock_response.json.return_value = { - "id": 1, - "nome": "Unidade 1", - } # Dados inválidos (não tem a chave "unidades") - - with patch( - "airflow_lappis.plugins.cliente_estrutura.ClienteBase.request", - return_value=(HTTPStatus.OK, mock_response.json.return_value), - ): - # Chama o método get_estrutura_organizacional_resumida - result = cliente_estrutura.get_estrutura_organizacional_resumida() - - # Verifica se o resultado é None (dados inválidos) - assert result is None - - -def test_get_estrutura_organizacional_resumida_with_params(cliente_estrutura): - # Configura o mock para retornar uma resposta bem-sucedida - mock_response = Mock() - mock_response.status_code = HTTPStatus.OK - mock_response.json.return_value = {"unidades": [{"id": 1, "nome": "Unidade 1"}]} - - with patch( - "airflow_lappis.plugins.cliente_estrutura.ClienteBase.request", - return_value=(HTTPStatus.OK, mock_response.json.return_value), - ): - # Chama o método get_estrutura_organizacional_resumida com parâmetros - result = cliente_estrutura.get_estrutura_organizacional_resumida( - codigo_poder="1", codigo_esfera="2", codigo_unidade="3" - ) - - # Verifica se o resultado é o esperado - assert result == [{"id": 1, "nome": "Unidade 1"}] - - # Verifica se os parâmetros foram passados corretamente - cliente_estrutura.request.assert_called_once_with( - http.HTTPMethod.GET, - "/estrutura-organizacional/resumida", - params={"codigoPoder": "1", "codigoEsfera": "2", "codigoUnidade": "3"}, - ) diff --git a/tests/test_cliente_postgres/test_create_table.py b/tests/test_cliente_postgres/test_create_table.py deleted file mode 100644 index afb7abf1..00000000 --- a/tests/test_cliente_postgres/test_create_table.py +++ /dev/null @@ -1,31 +0,0 @@ -from unittest.mock import patch, MagicMock -from airflow_lappis.plugins.cliente_postgres import ClientPostgresDB - - -@patch("psycopg2.connect") # Mockando a conexão com o banco -def test_create_table_if_not_exists(mock_connect): - # Criamos um mock para o cursor do banco - mock_conn = MagicMock() - mock_cursor = MagicMock() - mock_connect.return_value.__enter__.return_value = mock_conn - mock_conn.cursor.return_value.__enter__.return_value = mock_cursor - - # Dados fictícios para o teste - sample_data = {"id": 1, "name": "Alice"} - table_name = "users" - primary_key = ["id"] - schema = "public" - - # Criamos a instância da classe - db = ClientPostgresDB("fake_connection_string") - - # Chamamos o método que queremos testar - db.create_table_if_not_exists(sample_data, table_name, primary_key, schema) - - # Verificamos se os métodos corretos foram chamados no banco - mock_cursor.execute.assert_any_call(f"CREATE SCHEMA IF NOT EXISTS {schema};") - create_table_query = ( - f"CREATE TABLE IF NOT EXISTS {schema}.{table_name} (" - f"id TEXT, name TEXT, PRIMARY KEY (id));" - ) - mock_cursor.execute.assert_any_call(create_table_query) diff --git a/tests/test_cliente_postgres/test_drop_table.py b/tests/test_cliente_postgres/test_drop_table.py deleted file mode 100644 index f144fb94..00000000 --- a/tests/test_cliente_postgres/test_drop_table.py +++ /dev/null @@ -1,71 +0,0 @@ -from unittest.mock import patch, MagicMock -from airflow_lappis.plugins.cliente_postgres import ClientPostgresDB - - -@patch("airflow_lappis.plugins.cliente_postgres.psycopg2.connect") -def test_drop_table_if_exists(mock_connect): - # Criando uma instância mock do banco de dados - mock_conn = MagicMock() - mock_cursor = MagicMock() - - # Configurando o mock para retornar os objetos corretos - mock_connect.return_value = mock_conn # Simula a conexão - mock_conn.cursor.return_value = mock_cursor # Simula o cursor - - # Criando a instância do ClientPostgresDB com um conn_str fictício - db_client = ClientPostgresDB(conn_str="fake_connection_string") - - # Definindo os parâmetros para o teste - schema = "raw" - table_name = "test_table" - - # Chamando o método a ser testado - db_client.drop_table_if_exists(table_name, schema) - - # Verificando se a query foi executada corretamente - mock_cursor.execute.assert_called_once_with( - f"DROP TABLE IF EXISTS {schema}.{table_name};" - ) - - # Verificando se o commit foi chamado - mock_conn.commit.assert_called_once() - - # Verificando se o cursor e a conexão foram fechados - mock_cursor.close.assert_called_once() - mock_conn.close.assert_called_once() - - -@patch("airflow_lappis.plugins.cliente_postgres.psycopg2.connect") -def test_drop_table_if_exists_error(mock_connect): - # Criando uma instância mock do banco de dados - mock_conn = MagicMock() - mock_cursor = MagicMock() - - # Configurando o mock para simular um erro ao executar a query - mock_connect.return_value = mock_conn # Simula a conexão - mock_conn.cursor.return_value = mock_cursor # Simula o cursor - mock_cursor.execute.side_effect = Exception( - "Erro simulado" - ) # Simula um erro ao executar a query - - # Criando a instância do ClientPostgresDB com um conn_str fictício - db_client = ClientPostgresDB(conn_str="fake_connection_string") - - # Definindo os parâmetros para o teste - schema = "raw" - table_name = "test_table" - - # Chamando o método a ser testado - db_client.drop_table_if_exists(table_name, schema) - - # Verificando se a query foi executada corretamente - mock_cursor.execute.assert_called_once_with( - f"DROP TABLE IF EXISTS {schema}.{table_name};" - ) - - # Verificando se o commit NÃO foi chamado (já que houve um erro) - mock_conn.commit.assert_not_called() - - # Verificando se o cursor e a conexão foram fechados - mock_cursor.close.assert_called_once() - mock_conn.close.assert_called_once() diff --git a/tests/test_cliente_postgres/test_flatten_data.py b/tests/test_cliente_postgres/test_flatten_data.py deleted file mode 100644 index 0504d7ea..00000000 --- a/tests/test_cliente_postgres/test_flatten_data.py +++ /dev/null @@ -1,43 +0,0 @@ -from airflow_lappis.plugins.cliente_postgres import ClientPostgresDB - - -def test_flatten_data(): - db = ClientPostgresDB( - "fake_connection_string" - ) # Apenas passamos um valor qualquer para conn_str - - nested_data = [ - { - "id": 1, - "name": "Alice", - "address": {"city": "New York", "zip": "10001"}, - "emails": ["alice@example.com", "alice.work@example.com"], - }, - { - "id": 2, - "name": "Bob", - "address": {"city": "Los Angeles", "zip": "90001"}, - "emails": ["bob@example.com"], - }, - ] - - expected_output = [ - { - "id": 1, - "name": "Alice", - "address__city": "New York", - "address__zip": "10001", - "emails": "['alice@example.com', 'alice.work@example.com']", - }, - { - "id": 2, - "name": "Bob", - "address__city": "Los Angeles", - "address__zip": "90001", - "emails": "['bob@example.com']", - }, - ] - - result = db._flatten_data(nested_data) - - assert result == expected_output diff --git a/tests/test_cliente_postgres/test_get_contratos_ids.py b/tests/test_cliente_postgres/test_get_contratos_ids.py deleted file mode 100644 index 858e8309..00000000 --- a/tests/test_cliente_postgres/test_get_contratos_ids.py +++ /dev/null @@ -1,28 +0,0 @@ -from unittest.mock import patch, MagicMock -from airflow_lappis.plugins.cliente_postgres import ClientPostgresDB - - -@patch("airflow_lappis.plugins.cliente_postgres.psycopg2.connect") -def test_get_contratos_ids(mock_connect): - # Criando uma instância mock do banco de dados - mock_conn = MagicMock() - mock_cursor = MagicMock() - - # Configurando o mock para retornar os objetos corretos - mock_connect.return_value.__enter__.return_value = mock_conn - mock_conn.cursor.return_value.__enter__.return_value = mock_cursor - - # Definindo valores simulados que o banco retornaria - mock_cursor.fetchall.return_value = [(1,), (2,), (3,)] - - # Criando a instância do ClientPostgresDB com um conn_str fictício - db_client = ClientPostgresDB(conn_str="fake_connection_string") - - # Chamando o método a ser testado - contratos_ids = db_client.get_contratos_ids() - - # Verificando se a query foi executada corretamente - mock_cursor.execute.assert_called_once_with("SELECT id FROM raw.contratos") - - # Verificando se o retorno do método está correto - assert contratos_ids == [1, 2, 3] diff --git a/tests/test_cliente_postgres/test_insert_csv_data.py b/tests/test_cliente_postgres/test_insert_csv_data.py deleted file mode 100644 index bbe8e1f4..00000000 --- a/tests/test_cliente_postgres/test_insert_csv_data.py +++ /dev/null @@ -1,42 +0,0 @@ -from unittest.mock import patch, MagicMock -from airflow_lappis.plugins.cliente_postgres import ClientPostgresDB - - -@patch("airflow_lappis.plugins.cliente_postgres.psycopg2.connect") -def test_insert_csv_data(mock_connect): - # Criando uma instância mock do banco de dados - mock_conn = MagicMock() - mock_cursor = MagicMock() - - # Configurando o mock para retornar os objetos corretos - mock_connect.return_value.__enter__.return_value = mock_conn - mock_conn.cursor.return_value.__enter__.return_value = mock_cursor - - # Configurando encoding da conexão para evitar KeyError - mock_conn.encoding = "UTF8" - mock_cursor.connection.encoding = "UTF8" - - # Criando a instância do ClientPostgresDB com um conn_str fictício - db_client = ClientPostgresDB(conn_str="fake_connection_string") - - # Dados de entrada para o teste - csv_data = "id,name\n1,John Doe\n2,Jane Doe" - table_name = "users" - schema = "public" - - # Chamando o método a ser testado - with ( - patch.object(db_client, "drop_table_if_exists") as mock_drop_table_if_exists, - patch.object(db_client, "insert_data") as mock_insert_data, - ): - - db_client.insert_csv_data(csv_data, table_name, schema) - - # Verificando se drop_table_if_exists foi chamado corretamente - mock_drop_table_if_exists.assert_called_once_with(table_name, schema) - - # Verificando se insert_data foi chamado corretamente - expected_data = [{"id": 1, "name": "John Doe"}, {"id": 2, "name": "Jane Doe"}] - mock_insert_data.assert_called_once_with( - expected_data, table_name, primary_key=None, schema=schema - ) diff --git a/tests/test_cliente_postgres/test_insert_data.py b/tests/test_cliente_postgres/test_insert_data.py deleted file mode 100644 index d73d6e5e..00000000 --- a/tests/test_cliente_postgres/test_insert_data.py +++ /dev/null @@ -1,45 +0,0 @@ -from unittest.mock import patch, MagicMock -from airflow_lappis.plugins.cliente_postgres import ClientPostgresDB - - -@patch("airflow_lappis.plugins.cliente_postgres.psycopg2.connect") -def test_insert_data(mock_connect): - # Criando uma instância mock do banco de dados - mock_conn = MagicMock() - mock_cursor = MagicMock() - - # Configurando o mock para retornar os objetos corretos - mock_connect.return_value.__enter__.return_value = mock_conn - mock_conn.cursor.return_value.__enter__.return_value = mock_cursor - - # Configurando encoding da conexão para evitar KeyError - mock_conn.encoding = "UTF8" - mock_cursor.connection.encoding = "UTF8" - - # Criando a instância do ClientPostgresDB com um conn_str fictício - db_client = ClientPostgresDB(conn_str="fake_connection_string") - - # Dados de entrada para o teste - data = [{"id": 1, "name": "John Doe"}, {"id": 2, "name": "Jane Doe"}] - table_name = "users" - schema = "public" - conflict_fields = ["id"] - primary_key = ["id"] - - # Chamando o método a ser testado - with patch("psycopg2.extras.execute_values") as mock_execute_values: - db_client.insert_data(data, table_name, conflict_fields, primary_key, schema) - - # Verificando se a query foi construída corretamente - expected_sql = ( - f"INSERT INTO {schema}.{table_name} (id, name) VALUES %s " - "ON CONFLICT (id) DO UPDATE SET id = EXCLUDED.id, name = EXCLUDED.name" - ) - - # Verificando se execute_values foi chamado corretamente - mock_execute_values.assert_called_once_with( - mock_cursor, expected_sql, [(1, "John Doe"), (2, "Jane Doe")] - ) - - # Garantir que commit foi chamado - mock_conn.commit.assert_called_once() From dd58ba5a416bbce1fa938232e7c689f14e27d23f Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Wed, 5 Mar 2025 19:43:58 -0300 Subject: [PATCH 020/317] feat(dag): adiciona dag de insercao das notas de credito --- .../notas_de_credito_ingest_dag.py | 46 +++++++++++++++++++ airflow_lappis/plugins/cliente_ted.py | 28 +++++++++++ 2 files changed, 74 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/notas_de_credito_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/notas_de_credito_ingest_dag.py b/airflow_lappis/dags/data_ingest/notas_de_credito_ingest_dag.py new file mode 100644 index 00000000..4cb077ec --- /dev/null +++ b/airflow_lappis/dags/data_ingest/notas_de_credito_ingest_dag.py @@ -0,0 +1,46 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from postgres_helpers import get_postgres_conn +from cliente_postgres import ClientPostgresDB +from cliente_ted import ClienteTed + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Davi", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["notas de credito", "ted"], +) +def notas_de_credito_dag() -> None: + + @task + def fetch_and_store_notas_de_credito() -> None: + + api = ClienteTed() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + ug_codes = [113601, 113602] + + for ug_code in ug_codes: + notas_de_credito = api.get_notas_de_credito_by_ug(ug_code) + if notas_de_credito: + db.insert_data( + notas_de_credito, + "notas_de_credito", + conflict_fields=["id_nota"], + primary_key=["id_nota"], + schema="ted", + ) + else: + logging.warning(f"No notas de credito found for UG code: {ug_code}") + + fetch_and_store_notas_de_credito() + + +dag_instance = notas_de_credito_dag() diff --git a/airflow_lappis/plugins/cliente_ted.py b/airflow_lappis/plugins/cliente_ted.py index e00678ca..e419a5ff 100644 --- a/airflow_lappis/plugins/cliente_ted.py +++ b/airflow_lappis/plugins/cliente_ted.py @@ -51,3 +51,31 @@ def get_programa_by_id_programa(self, id_programa: str) -> list | None: f"{id_programa} with status: {status}" ) return None + + def get_notas_de_credito_by_ug(self, ug_code: int) -> list | None: + endpoint_1 = f"nota_credito?cd_ug_favorecida_nota=eq.{ug_code}" + endpoint_2 = f"nota_credito?cd_ug_emitente_nota=eq.{ug_code}" + + logging.info(f"Buscando notas de crédito para UG: {ug_code}") + + status_1, data_1 = self.request( + http.HTTPMethod.GET, endpoint_1, headers=self.BASE_HEADER + ) + status_2, data_2 = self.request( + http.HTTPMethod.GET, endpoint_2, headers=self.BASE_HEADER + ) + + if status_1 == http.HTTPStatus.OK and isinstance(data_1, list): + logging.info(f"Notas de crédito (favorecida) obtidas para UG {ug_code}") + else: + logging.warning(f"Falha ao buscar notas de crédito - Status: {status_1}") + data_1 = [] + + if status_2 == http.HTTPStatus.OK and isinstance(data_2, list): + logging.info(f"Notas de crédito (emitente) obtidas para UG {ug_code}") + else: + logging.warning(f"Falha ao buscar notas de crédito - Status: {status_2}") + data_2 = [] + + data = data_1 + data_2 + return data if data else None From 63f5df04b7677b0ff5577913740335c75a61aa54 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Thu, 6 Mar 2025 11:24:02 -0300 Subject: [PATCH 021/317] fix(insert): ajusta os parametros da insercao de teds --- airflow_lappis/dags/data_ingest/notas_de_credito_ingest_dag.py | 2 +- .../dags/data_ingest/programa_beneficiario_ingest_dag.py | 2 ++ airflow_lappis/dags/data_ingest/programas_ingest_dag.py | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/airflow_lappis/dags/data_ingest/notas_de_credito_ingest_dag.py b/airflow_lappis/dags/data_ingest/notas_de_credito_ingest_dag.py index 4cb077ec..a57a979b 100644 --- a/airflow_lappis/dags/data_ingest/notas_de_credito_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/notas_de_credito_ingest_dag.py @@ -15,7 +15,7 @@ "retries": 1, "retry_delay": timedelta(minutes=5), }, - tags=["notas de credito", "ted"], + tags=["notas de credito", "ted_api"], ) def notas_de_credito_dag() -> None: diff --git a/airflow_lappis/dags/data_ingest/programa_beneficiario_ingest_dag.py b/airflow_lappis/dags/data_ingest/programa_beneficiario_ingest_dag.py index 168ff914..f1f34cd5 100644 --- a/airflow_lappis/dags/data_ingest/programa_beneficiario_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/programa_beneficiario_ingest_dag.py @@ -41,6 +41,8 @@ def fetch_and_store_programa_beneficiario() -> None: db.insert_data( unique_id_programas, "beneficiario", + primary_key=["id_programa"], + conflict_fields=["id_programa"], schema="ted", ) else: diff --git a/airflow_lappis/dags/data_ingest/programas_ingest_dag.py b/airflow_lappis/dags/data_ingest/programas_ingest_dag.py index 4a9c203d..34e2b647 100644 --- a/airflow_lappis/dags/data_ingest/programas_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/programas_ingest_dag.py @@ -35,6 +35,8 @@ def fetch_and_store_programas() -> None: db.insert_data( programas, "programas", + primary_key=["tx_codigo_programa"], + conflict_fields=["tx_codigo_programa"], schema="ted", ) else: From 3333ef99ba603c81f36d257512fc24fa5b5fbd69 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Fri, 7 Mar 2025 14:53:15 -0300 Subject: [PATCH 022/317] feat(dag): adiciona dag de insercao da programacao financeira --- .../programacao_financeira_ingest_dag.py | 46 +++++++++++++++++++ airflow_lappis/plugins/cliente_ted.py | 32 +++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/programacao_financeira_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/programacao_financeira_ingest_dag.py b/airflow_lappis/dags/data_ingest/programacao_financeira_ingest_dag.py new file mode 100644 index 00000000..e0f4a1c5 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/programacao_financeira_ingest_dag.py @@ -0,0 +1,46 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from postgres_helpers import get_postgres_conn +from cliente_postgres import ClientPostgresDB +from cliente_ted import ClienteTed + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Davi", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["programacao_financeira", "ted_api"], +) +def programacao_financeira_dag() -> None: + + @task + def fetch_and_store_programacao_financeira() -> None: + + api = ClienteTed() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + ug_codes = [113601, 113602] + + for ug_code in ug_codes: + programacao_financeira = api.get_programacao_financeira_by_ug(ug_code) + if programacao_financeira: + db.insert_data( + programacao_financeira, + "programacao_financeira", + conflict_fields=["id_programacao"], + primary_key=["id_programacao"], + schema="ted", + ) + else: + logging.warning(f"No programacao financeira found for UG code: {ug_code}") + + fetch_and_store_programacao_financeira() + + +dag_instance = programacao_financeira_dag() diff --git a/airflow_lappis/plugins/cliente_ted.py b/airflow_lappis/plugins/cliente_ted.py index e419a5ff..d7bafe2d 100644 --- a/airflow_lappis/plugins/cliente_ted.py +++ b/airflow_lappis/plugins/cliente_ted.py @@ -79,3 +79,35 @@ def get_notas_de_credito_by_ug(self, ug_code: int) -> list | None: data = data_1 + data_2 return data if data else None + + def get_programacao_financeira_by_ug(self, ug_code: int) -> list | None: + endpoint_1 = f"programacao_financeira?ug_favorecida_programacao=eq.{ug_code}" + endpoint_2 = f"programacao_financeira?ug_emitente_programacao=eq.{ug_code}" + + logging.info(f"Buscando programação financeira para UG: {ug_code}") + + status_1, data_1 = self.request( + http.HTTPMethod.GET, endpoint_1, headers=self.BASE_HEADER + ) + status_2, data_2 = self.request( + http.HTTPMethod.GET, endpoint_2, headers=self.BASE_HEADER + ) + + if status_1 == http.HTTPStatus.OK and isinstance(data_1, list): + logging.info(f"Programação financeira (favorecida) obtida para UG {ug_code}") + else: + logging.warning( + f"Falha ao buscar programação financeira - Status: {status_1}" + ) + data_1 = [] + + if status_2 == http.HTTPStatus.OK and isinstance(data_2, list): + logging.info(f"Programação financeira (emitente) obtida para UG {ug_code}") + else: + logging.warning( + f"Falha ao buscar programação financeira - Status: {status_2}" + ) + data_2 = [] + + data = data_1 + data_2 + return data if data else None From c5bf052fd604ee6eab12ceac74bfc873168f722d Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Fri, 7 Mar 2025 15:20:27 -0300 Subject: [PATCH 023/317] hotfix(schema): ajusta os esquemas para os teds --- airflow_lappis/dags/data_ingest/notas_de_credito_ingest_dag.py | 2 +- .../dags/data_ingest/programa_beneficiario_ingest_dag.py | 2 +- .../dags/data_ingest/programacao_financeira_ingest_dag.py | 2 +- airflow_lappis/dags/data_ingest/programas_ingest_dag.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/notas_de_credito_ingest_dag.py b/airflow_lappis/dags/data_ingest/notas_de_credito_ingest_dag.py index a57a979b..cf829ff3 100644 --- a/airflow_lappis/dags/data_ingest/notas_de_credito_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/notas_de_credito_ingest_dag.py @@ -35,7 +35,7 @@ def fetch_and_store_notas_de_credito() -> None: "notas_de_credito", conflict_fields=["id_nota"], primary_key=["id_nota"], - schema="ted", + schema="transfere_gov", ) else: logging.warning(f"No notas de credito found for UG code: {ug_code}") diff --git a/airflow_lappis/dags/data_ingest/programa_beneficiario_ingest_dag.py b/airflow_lappis/dags/data_ingest/programa_beneficiario_ingest_dag.py index f1f34cd5..a1fb771c 100644 --- a/airflow_lappis/dags/data_ingest/programa_beneficiario_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/programa_beneficiario_ingest_dag.py @@ -43,7 +43,7 @@ def fetch_and_store_programa_beneficiario() -> None: "beneficiario", primary_key=["id_programa"], conflict_fields=["id_programa"], - schema="ted", + schema="transfere_gov", ) else: logging.warning( diff --git a/airflow_lappis/dags/data_ingest/programacao_financeira_ingest_dag.py b/airflow_lappis/dags/data_ingest/programacao_financeira_ingest_dag.py index e0f4a1c5..1a2ecc9d 100644 --- a/airflow_lappis/dags/data_ingest/programacao_financeira_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/programacao_financeira_ingest_dag.py @@ -35,7 +35,7 @@ def fetch_and_store_programacao_financeira() -> None: "programacao_financeira", conflict_fields=["id_programacao"], primary_key=["id_programacao"], - schema="ted", + schema="transfere_gov", ) else: logging.warning(f"No programacao financeira found for UG code: {ug_code}") diff --git a/airflow_lappis/dags/data_ingest/programas_ingest_dag.py b/airflow_lappis/dags/data_ingest/programas_ingest_dag.py index 34e2b647..38249ed2 100644 --- a/airflow_lappis/dags/data_ingest/programas_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/programas_ingest_dag.py @@ -37,7 +37,7 @@ def fetch_and_store_programas() -> None: "programas", primary_key=["tx_codigo_programa"], conflict_fields=["tx_codigo_programa"], - schema="ted", + schema="transfere_gov", ) else: logging.warning(f"No programas found for id_programas: {id_programas}") From 153f0bd7decf477144eb0f81d42e9207fa070af9 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Mon, 10 Mar 2025 22:14:43 -0300 Subject: [PATCH 024/317] fix(postgres): ajusta query de selecao dos beneficiarios --- airflow_lappis/plugins/cliente_postgres.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airflow_lappis/plugins/cliente_postgres.py b/airflow_lappis/plugins/cliente_postgres.py index 3f9edb5d..e1856c1f 100755 --- a/airflow_lappis/plugins/cliente_postgres.py +++ b/airflow_lappis/plugins/cliente_postgres.py @@ -191,7 +191,7 @@ def get_contratos_ids(self) -> List[int]: def get_id_programas(self) -> List[int]: """Extrai todos os IDs de programas da tabela beneficiário.""" - query = "SELECT id_programa FROM ted.beneficiario" + query = "SELECT id_programa FROM transfere_gov.beneficiario" with psycopg2.connect(self.conn_str) as conn: with conn.cursor() as cursor: From f8a04917cebe499b91bfedf927ee1b37cc9e97fe Mon Sep 17 00:00:00 2001 From: VictorSzk Date: Wed, 12 Mar 2025 20:30:53 +0000 Subject: [PATCH 025/317] Feat/nova gold --- .gitignore | 7 +- airflow_lappis/dags/dbt/ipea/dbt_project.yml | 2 +- .../dags/dbt/ipea/macros/create_udfs.sql | 2 + .../dbt/ipea/macros/udfs/f_parse_dates.sql | 44 +++++++++ .../macros/udfs/f_parse_financial_value.sql | 4 +- .../bronze/contratos.sql | 0 .../bronze/cronogramas.sql | 5 +- .../bronze/empenhos.sql | 2 + .../bronze/empenhos_tesouro.sql | 1 - .../bronze/estagios.sql | 2 +- .../bronze/faturas.sql | 2 + .../bronze/identificadores.sql | 2 + .../gold/contratos_comparativo_mensal.sql | 17 ++++ .../gold/contratos_resumo.sql | 0 .../silver/contratos_empenhos.sql | 0 .../silver/contratos_estagios.sql | 98 +++++++++++++++++++ .../silver/cronogramas_faturas_mensal.sql | 35 +++++++ .../contratos_dbt/silver/estagios_mensal.sql | 62 ++++++++++++ pyproject.toml | 1 + requirements.txt | 38 +++---- 20 files changed, 298 insertions(+), 26 deletions(-) create mode 100644 airflow_lappis/dags/dbt/ipea/macros/udfs/f_parse_dates.sql rename airflow_lappis/dags/dbt/ipea/models/{contratos => contratos_dbt}/bronze/contratos.sql (100%) rename airflow_lappis/dags/dbt/ipea/models/{contratos => contratos_dbt}/bronze/cronogramas.sql (86%) rename airflow_lappis/dags/dbt/ipea/models/{contratos => contratos_dbt}/bronze/empenhos.sql (98%) rename airflow_lappis/dags/dbt/ipea/models/{contratos => contratos_dbt}/bronze/empenhos_tesouro.sql (99%) rename airflow_lappis/dags/dbt/ipea/models/{contratos => contratos_dbt}/bronze/estagios.sql (97%) rename airflow_lappis/dags/dbt/ipea/models/{contratos => contratos_dbt}/bronze/faturas.sql (98%) rename airflow_lappis/dags/dbt/ipea/models/{contratos => contratos_dbt}/bronze/identificadores.sql (97%) create mode 100644 airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql rename airflow_lappis/dags/dbt/ipea/models/{contratos => contratos_dbt}/gold/contratos_resumo.sql (100%) rename airflow_lappis/dags/dbt/ipea/models/{contratos => contratos_dbt}/silver/contratos_empenhos.sql (100%) create mode 100644 airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_estagios.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/cronogramas_faturas_mensal.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/estagios_mensal.sql diff --git a/.gitignore b/.gitignore index c4d5ff96..15898217 100644 --- a/.gitignore +++ b/.gitignore @@ -46,4 +46,9 @@ logs/ # System files .DS_Store -Thumbs.db \ No newline at end of file +Thumbs.db + +# VPN +/connect.sh +*.ovpn +*.p12 \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/ipea/dbt_project.yml b/airflow_lappis/dags/dbt/ipea/dbt_project.yml index 35338d41..3ac28327 100755 --- a/airflow_lappis/dags/dbt/ipea/dbt_project.yml +++ b/airflow_lappis/dags/dbt/ipea/dbt_project.yml @@ -19,7 +19,7 @@ clean-targets: models: ipea: - contratos: + contratos_dbt: +materialized: table +database: analytics +schema: contratos diff --git a/airflow_lappis/dags/dbt/ipea/macros/create_udfs.sql b/airflow_lappis/dags/dbt/ipea/macros/create_udfs.sql index f5f5347c..a5db3274 100644 --- a/airflow_lappis/dags/dbt/ipea/macros/create_udfs.sql +++ b/airflow_lappis/dags/dbt/ipea/macros/create_udfs.sql @@ -4,5 +4,7 @@ create schema if not exists {{ target.schema }}; {{ create_f_parse_financial_value() }} ; + {{ create_f_parse_dates() }} + ; {% endmacro %} diff --git a/airflow_lappis/dags/dbt/ipea/macros/udfs/f_parse_dates.sql b/airflow_lappis/dags/dbt/ipea/macros/udfs/f_parse_dates.sql new file mode 100644 index 00000000..5297789e --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/macros/udfs/f_parse_dates.sql @@ -0,0 +1,44 @@ +-- Essa fun +{% macro create_f_parse_dates() %} + + create or replace function {{ target.schema }}.parse_date(in_text text) + returns date + as + $$ + + with + + split_column as ( + select + split_part(in_text, '/', 1) as mes, + split_part(in_text, '/', 2) as ano + ), + + fixed_month as ( + select + ano, + case + when mes = 'JAN' then '01' + when mes = 'FEV' then '02' + when mes = 'MAR' then '03' + when mes = 'ABR' then '04' + when mes = 'MAI' then '05' + when mes = 'JUN' then '06' + when mes = 'JUL' then '07' + when mes = 'AGO' then '08' + when mes = 'SET' then '09' + when mes = 'OUT' then '10' + when mes = 'NOV' then '11' + when mes = 'DEZ' then '12' + else mes end as mes_num + from split_column + ) + + select + to_date(ano::numeric - 1 || '-' || '12', 'YYYY-MM') + (mes_num || ' months')::interval as result + from fixed_month + $$ + language sql + ; + +{% endmacro %} diff --git a/airflow_lappis/dags/dbt/ipea/macros/udfs/f_parse_financial_value.sql b/airflow_lappis/dags/dbt/ipea/macros/udfs/f_parse_financial_value.sql index 17c58951..7a319000 100644 --- a/airflow_lappis/dags/dbt/ipea/macros/udfs/f_parse_financial_value.sql +++ b/airflow_lappis/dags/dbt/ipea/macros/udfs/f_parse_financial_value.sql @@ -5,7 +5,9 @@ as $$ select - case when in_text like '(%' then regexp_replace(replace(coalesce(in_text, '0'), '.', ''), '(\()?(\d+),(\d+)(\))?', '-\2.\3')::numeric(15,2) + case + when in_text like '%NaN%' then 0.00::numeric(15,2) + when in_text like '(%' then regexp_replace(replace(coalesce(in_text, '0'), '.', ''), '(\()?(\d+),(\d+)(\))?', '-\2.\3')::numeric(15,2) else replace(replace(coalesce(in_text, '0'), '.', ''), ',', '.')::numeric(15,2) end as result $$ language sql diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/contratos.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/contratos.sql similarity index 100% rename from airflow_lappis/dags/dbt/ipea/models/contratos/bronze/contratos.sql rename to airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/contratos.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/cronogramas.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/cronogramas.sql similarity index 86% rename from airflow_lappis/dags/dbt/ipea/models/contratos/bronze/cronogramas.sql rename to airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/cronogramas.sql index 88553283..30f644c3 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/cronogramas.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/cronogramas.sql @@ -1,3 +1,5 @@ +{{ config(unikey_key="id") }} + select id::integer as id, contrato_id::integer as contrato_id, @@ -9,6 +11,5 @@ select anoref::integer as anoref, retroativo::text as retroativo, replace(replace(valor::text, '.', ''), ',', '.')::numeric(15, 2) as valor, - vencimento::date as vencimento, - now() as inserted_at + vencimento::date as vencimento from {{ source("compras_gov", "cronograma") }} diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos.sql similarity index 98% rename from airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos.sql rename to airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos.sql index e6d0f50f..fa631dbf 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos.sql @@ -1,3 +1,5 @@ +{{ config(unikey_key="id") }} + with empenhos as ( select diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos_tesouro.sql similarity index 99% rename from airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql rename to airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos_tesouro.sql index fc9b2fa1..42f05bc2 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/empenhos_tesouro.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos_tesouro.sql @@ -1,5 +1,4 @@ with - empenhos_raw as ( select ne_ccor::text as ne_ccor, diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/estagios.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/estagios.sql similarity index 97% rename from airflow_lappis/dags/dbt/ipea/models/contratos/bronze/estagios.sql rename to airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/estagios.sql index ca73d292..18aa1f1e 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/estagios.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/estagios.sql @@ -31,7 +31,7 @@ with mes_lancamento, case when length(ne_num_processo::text) > 3 - then regexp_replace(ltrim(ne_num_processo::text, '0'), '[\./-]', '', 'g') + then regexp_replace(ne_num_processo::text, '[\./-]', '', 'g') else ne_num_processo::text end as ne_num_processo, diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/faturas.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/faturas.sql similarity index 98% rename from airflow_lappis/dags/dbt/ipea/models/contratos/bronze/faturas.sql rename to airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/faturas.sql index 30fc301b..f6376fad 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/faturas.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/faturas.sql @@ -1,3 +1,5 @@ +{{ config(unikey_key="id") }} + with faturas_raw as ( select diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/identificadores.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/identificadores.sql similarity index 97% rename from airflow_lappis/dags/dbt/ipea/models/contratos/bronze/identificadores.sql rename to airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/identificadores.sql index ecc0f6ae..4a897528 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos/bronze/identificadores.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/identificadores.sql @@ -21,6 +21,7 @@ with contratos as ( select id::text as contrato_id, + categoria, case when length(numero) = 12 then numero end as ne, regexp_replace(processo, '[^0-9]', '', 'g') as processo, regexp_replace(fornecedor_cnpj_cpf_idgener, '[/.-]', '', 'g') as cnpj_cpf, @@ -46,6 +47,7 @@ with identificadores as ( select c.contrato_id, + c.categoria, c.processo, c.cnpj_cpf, c.info_complementar, diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql new file mode 100644 index 00000000..998a1c17 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql @@ -0,0 +1,17 @@ +with + + siafi_data as (select * from {{ ref("contratos_estagios") }}), + + compras_gov_data as (select * from {{ ref("cronogramas_faturas_mensal") }}) + +select + c.contrato_id, + c.mes_ref, + c.valor_cronograma as comprasgov_valor_cronograma, + c.valor_faturas as comprasgov_valor_faturas, + s.valor_empenhado as siafi_valor_empenhado, + s.valor_liquidado as siafi_valor_liquidado, + s.valor_pago as siafi_valor_pago +from compras_gov_data as c +left join + siafi_data as s on c.contrato_id = s.contrato_id and c.mes_ref = s.mes_lancamento diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/gold/contratos_resumo.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_resumo.sql similarity index 100% rename from airflow_lappis/dags/dbt/ipea/models/contratos/gold/contratos_resumo.sql rename to airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_resumo.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos/silver/contratos_empenhos.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_empenhos.sql similarity index 100% rename from airflow_lappis/dags/dbt/ipea/models/contratos/silver/contratos_empenhos.sql rename to airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_empenhos.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_estagios.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_estagios.sql new file mode 100644 index 00000000..aa08ef10 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_estagios.sql @@ -0,0 +1,98 @@ +with + + ids_filtrados as ( + select contrato_id, ne, cnpj_cpf, processo, info_complementar + from {{ ref("identificadores") }} + where categoria not in ('Cessão') + ), + + -- Join 1 + id_table_1 as (select distinct contrato_id, ne, cnpj_cpf from ids_filtrados), + + joined_table_1 as ( + select + contrato_id, + cnpj_cpf, + ne, + num_processo, + info_complementar, + mes_lancamento, + valor_empenhado, + valor_liquidado, + valor_pago + from {{ ref("estagios_mensal") }} + left join id_table_1 using (ne, cnpj_cpf) + ), + + -- Part 2 + empenhos_restantes_1 as (select * from joined_table_1 where contrato_id is null), + + id_table_2 as ( + select distinct contrato_id, cnpj_cpf, processo as num_processo + from ids_filtrados l + where + not exists ( + select distinct contrato_id + from joined_table_1 r + where r.contrato_id = l.contrato_id + ) + ), + + joined_table_2 as ( + select + r.contrato_id, + cnpj_cpf, + ne, + num_processo, + info_complementar, + mes_lancamento, + valor_empenhado, + valor_liquidado, + valor_pago + from empenhos_restantes_1 l + left join id_table_2 r using (cnpj_cpf, num_processo) + ), + + -- Part 3 + empenhos_restantes_2 as (select * from joined_table_2 where contrato_id is null), + + id_table_3 as ( + select distinct t0.contrato_id, t0.cnpj_cpf, t0.info_complementar + from ids_filtrados t0 + left join id_table_1 t1 on t0.contrato_id = t1.contrato_id + left join id_table_2 t2 on t0.contrato_id = t2.contrato_id + where t1.contrato_id is null or t2.contrato_id is null + + ), + + joined_table_3 as ( + select + r.contrato_id, + cnpj_cpf, + ne, + num_processo, + info_complementar, + mes_lancamento, + valor_empenhado, + valor_liquidado, + valor_pago + from empenhos_restantes_2 l + left join id_table_3 r using (cnpj_cpf, info_complementar) + ), + + result_table as ( + select * + from joined_table_1 + union + select * + from joined_table_2 + union + select * + from joined_table_3 + ) + +-- +select contrato_id, mes_lancamento, valor_empenhado, valor_liquidado, valor_pago +from result_table +where contrato_id is not null +order by contrato_id, mes_lancamento diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/cronogramas_faturas_mensal.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/cronogramas_faturas_mensal.sql new file mode 100644 index 00000000..de9a94bf --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/cronogramas_faturas_mensal.sql @@ -0,0 +1,35 @@ +with + + cronograma_agg as ( + select contrato_id, vencimento as mes_ref, sum(valor) as valor_cronograma + from {{ ref("cronogramas") }} + group by 1, 2 + order by contrato_id, vencimento + ), + + faturas_agg as ( + select + contrato_id, + to_date( + split_part(emissao::text, '-', 1) + || '-' + || split_part(emissao::text, '-', 2), + 'YYYY-MM' + ) as mes_ref, + sum(juros + multa + glosa + valorliquido) as valor_faturas + from {{ ref("faturas") }} + group by 1, 2 + ), + + joined_table as ( + select * from cronograma_agg left join faturas_agg using (contrato_id, mes_ref) + ) + +-- +select + contrato_id::text, + mes_ref, + coalesce(valor_cronograma, 0) as valor_cronograma, + coalesce(valor_faturas, 0) as valor_faturas +from joined_table +order by contrato_id, mes_ref diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/estagios_mensal.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/estagios_mensal.sql new file mode 100644 index 00000000..087c5812 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/estagios_mensal.sql @@ -0,0 +1,62 @@ +with + + parsed_estagios as ( + select + right(ne_ccor, 12) as ne, + mes_lancamento, + ne_ccor_favorecido as cnpj_cpf, + substring(ne_informacao_complementar, '(^[0-9]+)') as info_complementar, + ne_num_processo, + despesas_empenhadas_controle_empenho_saldo_moeda_origem valor_empenhado, + despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem + as valor_liquidado, + despesas_pagas_controle_empenho_movim_liquido_moeda_origem as valor_pago + from {{ ref("estagios") }} + where true and ne_ccor != 'Total' and mes_lancamento not like '01%' + ), + + grouped_estagios as ( + select + ne, + mes_lancamento, + cnpj_cpf, + max(info_complementar) as info_complementar, + max(ne_num_processo) as num_processo, + sum(valor_empenhado) as valor_empenhado, + sum(valor_liquidado) as valor_liquidado, + sum(valor_pago) as valor_pago + from parsed_estagios + group by 1, 2, 3 + order by 1, 2 + ), + + processo_fixed as ( + select + ne, + cnpj_cpf, + info_complementar, + parse_date(mes_lancamento) as mes_lancamento, + min(num_processo) over (partition by ne) as num_processo, + valor_empenhado, + valor_liquidado, + valor_pago + from grouped_estagios + ), + + cummulative_values as ( + select + ne, + cnpj_cpf, + info_complementar, + mes_lancamento, + num_processo, + valor_empenhado, + valor_liquidado, + valor_pago + from processo_fixed + ) + +-- +select * +from cummulative_values +order by ne, cnpj_cpf, mes_lancamento diff --git a/pyproject.toml b/pyproject.toml index 5ff8afb3..74d883aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,6 +69,7 @@ exclude = ''' | \.mypy_cache | \.tox | \.venv + | \.conda | _build | buck-out | build diff --git a/requirements.txt b/requirements.txt index eeb08c41..29a31feb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,9 @@ aenum==3.1.15 ; python_version >= "3.11" and python_version < "3.12" agate==1.9.1 ; python_version >= "3.11" and python_version < "3.12" -aiohappyeyeballs==2.4.6 ; python_version >= "3.11" and python_version < "3.12" +aiohappyeyeballs==2.5.0 ; python_version >= "3.11" and python_version < "3.12" aiohttp==3.10.11 ; python_version >= "3.11" and python_version < "3.12" aiosignal==1.3.2 ; python_version >= "3.11" and python_version < "3.12" -alembic==1.14.1 ; python_version >= "3.11" and python_version < "3.12" +alembic==1.15.1 ; python_version >= "3.11" and python_version < "3.12" annotated-types==0.7.0 ; python_version >= "3.11" and python_version < "3.12" anyio==4.8.0 ; python_version >= "3.11" and python_version < "3.12" apache-airflow-providers-common-io==1.4.2 ; python_version >= "3.11" and python_version < "3.12" @@ -15,9 +15,9 @@ apache-airflow-providers-postgres==5.14.0 ; python_version >= "3.11" and python_ apache-airflow-providers-sqlite==3.9.1 ; python_version >= "3.11" and python_version < "3.12" apache-airflow==2.8.1 ; python_version >= "3.11" and python_version < "3.12" apispec[yaml]==6.8.1 ; python_version >= "3.11" and python_version < "3.12" -argcomplete==3.5.3 ; python_version >= "3.11" and python_version < "3.12" +argcomplete==3.6.0 ; python_version >= "3.11" and python_version < "3.12" asgiref==3.8.1 ; python_version >= "3.11" and python_version < "3.12" -astronomer-cosmos==1.8.2 ; python_version >= "3.11" and python_version < "3.12" +astronomer-cosmos==1.9.0 ; python_version >= "3.11" and python_version < "3.12" attrs==25.1.0 ; python_version >= "3.11" and python_version < "3.12" babel==2.17.0 ; python_version >= "3.11" and python_version < "3.12" blinker==1.9.0 ; python_version >= "3.11" and python_version < "3.12" @@ -33,16 +33,17 @@ configupdater==3.2 ; python_version >= "3.11" and python_version < "3.12" connexion[flask]==2.14.1 ; python_version >= "3.11" and python_version < "3.12" cron-descriptor==1.4.5 ; python_version >= "3.11" and python_version < "3.12" croniter==6.0.0 ; python_version >= "3.11" and python_version < "3.12" -cryptography==44.0.1 ; python_version >= "3.11" and python_version < "3.12" +cryptography==44.0.2 ; python_version >= "3.11" and python_version < "3.12" daff==1.3.46 ; python_version >= "3.11" and python_version < "3.12" -dbt-adapters==1.14.0 ; python_version >= "3.11" and python_version < "3.12" +dbt-adapters==1.14.1 ; python_version >= "3.11" and python_version < "3.12" dbt-common==1.15.0 ; python_version >= "3.11" and python_version < "3.12" -dbt-core==1.9.2 ; python_version >= "3.11" and python_version < "3.12" +dbt-core==1.9.3 ; python_version >= "3.11" and python_version < "3.12" dbt-extractor==0.5.1 ; python_version >= "3.11" and python_version < "3.12" dbt-postgres==1.9.0 ; python_version >= "3.11" and python_version < "3.12" dbt-semantic-interfaces==0.7.4 ; python_version >= "3.11" and python_version < "3.12" deepdiff==7.0.1 ; python_version >= "3.11" and python_version < "3.12" deprecated==1.2.18 ; python_version >= "3.11" and python_version < "3.12" +deprecation==2.1.0 ; python_version >= "3.11" and python_version < "3.12" dill==0.3.9 ; python_version >= "3.11" and python_version < "3.12" distlib==0.3.9 ; python_version >= "3.11" and python_version < "3.12" dnspython==2.7.0 ; python_version >= "3.11" and python_version < "3.12" @@ -50,7 +51,7 @@ email-validator==1.3.1 ; python_version >= "3.11" and python_version < "3.12" filelock==3.17.0 ; python_version >= "3.11" and python_version < "3.12" flask-appbuilder==4.3.10 ; python_version >= "3.11" and python_version < "3.12" flask-babel==2.0.0 ; python_version >= "3.11" and python_version < "3.12" -flask-caching==2.0.1 ; python_version >= "3.11" and python_version < "3.12" +flask-caching==2.3.1 ; python_version >= "3.11" and python_version < "3.12" flask-jwt-extended==4.7.1 ; python_version >= "3.11" and python_version < "3.12" flask-limiter==3.10.1 ; python_version >= "3.11" and python_version < "3.12" flask-login==0.6.3 ; python_version >= "3.11" and python_version < "3.12" @@ -59,11 +60,11 @@ flask-sqlalchemy==2.5.1 ; python_version >= "3.11" and python_version < "3.12" flask-wtf==1.2.2 ; python_version >= "3.11" and python_version < "3.12" flask==2.2.5 ; python_version >= "3.11" and python_version < "3.12" frozenlist==1.5.0 ; python_version >= "3.11" and python_version < "3.12" -fsspec==2025.2.0 ; python_version >= "3.11" and python_version < "3.12" +fsspec==2025.3.0 ; python_version >= "3.11" and python_version < "3.12" google-re2==1.1.20240702 ; python_version >= "3.11" and python_version < "3.12" -googleapis-common-protos==1.67.0 ; python_version >= "3.11" and python_version < "3.12" +googleapis-common-protos==1.69.1 ; python_version >= "3.11" and python_version < "3.12" greenlet==3.1.1 ; python_version >= "3.11" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32") and python_version < "3.12" -grpcio==1.70.0 ; python_version >= "3.11" and python_version < "3.12" +grpcio==1.71.0 ; python_version >= "3.11" and python_version < "3.12" gunicorn==23.0.0 ; python_version >= "3.11" and python_version < "3.12" h11==0.14.0 ; python_version >= "3.11" and python_version < "3.12" httpcore==1.0.7 ; python_version >= "3.11" and python_version < "3.12" @@ -74,12 +75,12 @@ importlib-metadata==6.11.0 ; python_version >= "3.11" and python_version < "3.12 inflection==0.5.1 ; python_version >= "3.11" and python_version < "3.12" isodate==0.6.1 ; python_version >= "3.11" and python_version < "3.12" itsdangerous==2.2.0 ; python_version >= "3.11" and python_version < "3.12" -jinja2==3.1.5 ; python_version >= "3.11" and python_version < "3.12" +jinja2==3.1.6 ; python_version >= "3.11" and python_version < "3.12" jsonschema-specifications==2024.10.1 ; python_version >= "3.11" and python_version < "3.12" jsonschema==4.23.0 ; python_version >= "3.11" and python_version < "3.12" lazy-object-proxy==1.10.0 ; python_version >= "3.11" and python_version < "3.12" leather==0.4.0 ; python_version >= "3.11" and python_version < "3.12" -limits==4.0.1 ; python_version >= "3.11" and python_version < "3.12" +limits==4.1 ; python_version >= "3.11" and python_version < "3.12" linkify-it-py==2.0.3 ; python_version >= "3.11" and python_version < "3.12" lockfile==0.12.2 ; python_version >= "3.11" and python_version < "3.12" lxml==5.3.1 ; python_version >= "3.11" and python_version < "3.12" @@ -115,7 +116,7 @@ pendulum==3.0.0 ; python_version >= "3.11" and python_version < "3.12" platformdirs==4.3.6 ; python_version >= "3.11" and python_version < "3.12" pluggy==1.5.0 ; python_version >= "3.11" and python_version < "3.12" prison==0.2.1 ; python_version >= "3.11" and python_version < "3.12" -propcache==0.2.1 ; python_version >= "3.11" and python_version < "3.12" +propcache==0.3.0 ; python_version >= "3.11" and python_version < "3.12" protobuf==5.29.3 ; python_version >= "3.11" and python_version < "3.12" psutil==7.0.0 ; python_version >= "3.11" and python_version < "3.12" psycopg2-binary==2.9.10 ; python_version >= "3.11" and python_version < "3.12" @@ -138,11 +139,11 @@ requests==2.32.3 ; python_version >= "3.11" and python_version < "3.12" rfc3339-validator==0.1.4 ; python_version >= "3.11" and python_version < "3.12" rich-argparse==1.7.0 ; python_version >= "3.11" and python_version < "3.12" rich==13.9.4 ; python_version >= "3.11" and python_version < "3.12" -rpds-py==0.22.3 ; python_version >= "3.11" and python_version < "3.12" -setproctitle==1.3.4 ; python_version >= "3.11" and python_version < "3.12" +rpds-py==0.23.1 ; python_version >= "3.11" and python_version < "3.12" +setproctitle==1.3.5 ; python_version >= "3.11" and python_version < "3.12" six==1.17.0 ; python_version >= "3.11" and python_version < "3.12" sniffio==1.3.1 ; python_version >= "3.11" and python_version < "3.12" -snowplow-tracker==1.0.4 ; python_version >= "3.11" and python_version < "3.12" +snowplow-tracker==1.1.0 ; python_version >= "3.11" and python_version < "3.12" sqlalchemy-jsonfield==1.0.2 ; python_version >= "3.11" and python_version < "3.12" sqlalchemy-utils==0.41.2 ; python_version >= "3.11" and python_version < "3.12" sqlalchemy==1.4.54 ; python_version >= "3.11" and python_version < "3.12" @@ -151,14 +152,13 @@ tabulate==0.9.0 ; python_version >= "3.11" and python_version < "3.12" tenacity==9.0.0 ; python_version >= "3.11" and python_version < "3.12" termcolor==2.5.0 ; python_version >= "3.11" and python_version < "3.12" text-unidecode==1.3 ; python_version >= "3.11" and python_version < "3.12" -types-requests==2.32.0.20241016 ; python_version >= "3.11" and python_version < "3.12" typing-extensions==4.12.2 ; python_version >= "3.11" and python_version < "3.12" tzdata==2025.1 ; python_version >= "3.11" and python_version < "3.12" uc-micro-py==1.0.3 ; python_version >= "3.11" and python_version < "3.12" unicodecsv==0.14.1 ; python_version >= "3.11" and python_version < "3.12" universal-pathlib==0.2.6 ; python_version >= "3.11" and python_version < "3.12" urllib3==2.3.0 ; python_version >= "3.11" and python_version < "3.12" -virtualenv==20.29.2 ; python_version >= "3.11" and python_version < "3.12" +virtualenv==20.29.3 ; python_version >= "3.11" and python_version < "3.12" werkzeug==2.3.8 ; python_version >= "3.11" and python_version < "3.12" wrapt==1.17.2 ; python_version >= "3.11" and python_version < "3.12" wtforms==3.2.1 ; python_version >= "3.11" and python_version < "3.12" From ae2c58945a0cbec1d2abde62a74c7f6c74d365ba Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Mon, 17 Mar 2025 17:55:35 +0000 Subject: [PATCH 026/317] Siafi/programacao financeira --- ...programacao_financeira_siafi_ingest_dag.py | 48 ++++++ airflow_lappis/plugins/cliente_postgres.py | 17 ++ airflow_lappis/plugins/cliente_siafi.py | 160 ++++++++++-------- 3 files changed, 157 insertions(+), 68 deletions(-) create mode 100644 airflow_lappis/dags/data_ingest/programacao_financeira_siafi_ingest_dag.py mode change 100755 => 100644 airflow_lappis/plugins/cliente_siafi.py diff --git a/airflow_lappis/dags/data_ingest/programacao_financeira_siafi_ingest_dag.py b/airflow_lappis/dags/data_ingest/programacao_financeira_siafi_ingest_dag.py new file mode 100644 index 00000000..df6d0f1e --- /dev/null +++ b/airflow_lappis/dags/data_ingest/programacao_financeira_siafi_ingest_dag.py @@ -0,0 +1,48 @@ +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from cliente_siafi import ClienteSiafi +from cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn + + +@dag( + schedule_interval="@daily", + start_date=datetime(2024, 3, 12), + catchup=False, + default_args={ + "owner": "Davi", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["programacao_financeira", "siafi_api"], +) +def programacao_financeira_siafi_dag() -> None: + @task + def fetch_and_store_programacao_financeira() -> None: + cliente_siafi = ClienteSiafi() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + programacoes = db.get_programacao_financeira() + + for programacao in programacoes: + tx_numero_programacao, ug_emitente_programacao = programacao + ano = int(str(tx_numero_programacao)[:4]) + num_lista = int(str(tx_numero_programacao)[-6:]) + ug_emitente = int(ug_emitente_programacao) + + response = cliente_siafi.consultar_programacao_financeira( + ug_emitente, ano, num_lista + ) + if response: + db.insert_data( + [response], + "programacao_financeira_siafi", + conflict_fields=["TRF__numeroDocumento"], + primary_key=["TRF__numeroDocumento"], + schema="siafi", + ) + + fetch_and_store_programacao_financeira() + + +dag_instance = programacao_financeira_siafi_dag() diff --git a/airflow_lappis/plugins/cliente_postgres.py b/airflow_lappis/plugins/cliente_postgres.py index e1856c1f..b8990f71 100755 --- a/airflow_lappis/plugins/cliente_postgres.py +++ b/airflow_lappis/plugins/cliente_postgres.py @@ -1,6 +1,7 @@ import logging from typing import Any, Dict, List, Optional, Tuple import psycopg2 +import psycopg2.extras # Added import for execute_values from pandas import json_normalize import pandas as pd import io @@ -237,3 +238,19 @@ def insert_csv_data( # Insere os novos dados self.insert_data(data, table_name, primary_key=None, schema=schema) + + def get_programacao_financeira(self) -> List[Tuple[Any, ...]]: + """Extrai o numero_programacao e ug_emitente da tabela programacao_financeira. + + Returns: + List[Tuple[Any, ...]]: Lista de tuplas com numero_programacao e ug_emitente + """ + query = ( + "SELECT tx_numero_programacao, ug_emitente_programacao " + "FROM transfere_gov.programacao_financeira" + ) + with psycopg2.connect(self.conn_str) as conn: + with conn.cursor() as cursor: + cursor.execute(query) + programacao_financeira = cursor.fetchall() + return programacao_financeira diff --git a/airflow_lappis/plugins/cliente_siafi.py b/airflow_lappis/plugins/cliente_siafi.py old mode 100755 new mode 100644 index 108f0449..6715369f --- a/airflow_lappis/plugins/cliente_siafi.py +++ b/airflow_lappis/plugins/cliente_siafi.py @@ -1,90 +1,114 @@ -import json +import os import logging -from typing import Any -import requests -from httpx import HTTPStatusError +from zeep import Client +from zeep.transports import Transport +from zeep.wsse.username import UsernameToken +from requests import Session +from typing import Dict, Any, Optional -from cliente_base import ClienteBase +# Configuração do logger +logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" +) +logger = logging.getLogger(__name__) -class ClienteSiafi(ClienteBase): - - BEARER_ENDPOINT = "https://gateway.apiserpro.serpro.gov.br/token" - SIAFI_ENDPOINT = "https://gateway.apiserpro.serpro.gov.br/api-integra-siafi/api/v2" - - def __init__( - self, bearer_key: str, bearer_secret: str, siafi_credential: str - ) -> None: - headers = ClienteSiafi._setup_headers( - ClienteSiafi.BEARER_ENDPOINT, bearer_key, bearer_secret, siafi_credential - ) - super().__init__(base_url=ClienteSiafi.SIAFI_ENDPOINT, headers=headers) +class ClienteSiafi: + def __init__(self) -> None: + """ + Inicializa o cliente SIAFI com as configurações necessárias. + """ + self.base_url = "https://servicos-siafi.tesouro.gov.br/siafi" + self.cert_path = os.getenv("SIAFI_CERT_PATH") + self.key_path = os.getenv("SIAFI_KEY_PATH") + self.siafi_username = os.getenv("SIAFI_USERNAME") + self.siafi_password = os.getenv("SIAFI_PASSWORD") - @staticmethod - def _get_token(url: str, consumer_key: str, consumer_secret: str) -> str: + def _criar_cliente_soap(self, ano: int, endpoint: str) -> Optional[Client]: """ - Gets token from token endpoint. + Cria e retorna um cliente SOAP para comunicação com o serviço SIAFI + com a URL específica para o ano e endpoint informados. Args: - url: Token endpoint. - consumer_key: Consumer key. - consumer_secret: Consumer secret. + ano (int): Ano para formar a URL do WSDL + endpoint (str): Endpoint específico do serviço SIAFI Returns: - str: The token. + Client: Cliente SOAP configurado ou None em caso de falha. """ - response = requests.post( - url, - data={"grant_type": "client_credentials"}, - auth=(consumer_key, consumer_secret), - ) - data = response.json() - return str(data.get("access_token", "")) - - @staticmethod - def _setup_headers( - bearer_endpoint: str, bearer_key: str, bearer_secret: str, siafi_credential: str - ) -> dict: - """ - Setups the headers for the client. + wsdl_url = f"{self.base_url}{ano}/{endpoint}?wsdl" + logger.info(f"Criando cliente SOAP com URL: {wsdl_url}") - Args: - bearer_endpoint: Bearer endpoint. - bearer_key: Bearer key. - bearer_secret: Bearer secret. - siafi_credential: Credencial SIAFI. + if not isinstance(self.cert_path, str) or not isinstance(self.key_path, str): + logger.error("Certificados SSL inválidos.") + return None - Returns: - dict: The headers. - """ - bearer_token = ClienteSiafi._get_token( - url=bearer_endpoint, consumer_key=bearer_key, consumer_secret=bearer_secret - ) - headers = { - "Authorization": f"Bearer {bearer_token}", - "Content-Type": "application/json", - "x-credencial": siafi_credential, - } - return headers + session = Session() + session.verify = self.cert_path + session.cert = (self.cert_path, self.key_path) + + transport = Transport(session=session) + wsse = UsernameToken(self.siafi_username, self.siafi_password, use_digest=False) + + try: + client = Client(wsdl_url, transport=transport, wsse=wsse) + logger.info( + f"Cliente SOAP para o ano {ano} e endpoint {endpoint} criado com sucesso." + ) + return client + except Exception as e: + logger.error( + f"Erro ao criar o cliente SOAP para ano {ano} e endpoint {endpoint}: {e}" + ) + return None - def get(self, endpoint: str) -> Any: + def consultar_programacao_financeira( + self, ug_emitente: str, ano: int, num_lista: str + ) -> Optional[Dict[str, Any]]: """ - Makes a GET request to the siafi endpoint. + Consulta programações financeiras no SIAFI. Args: - endpoint: The endpoint. + ug_emitente (str): UG emitente da programação financeira. + ano (int): Ano da programação financeira. + num_lista (str): Número do documento da programação financeira. Returns: - Any: The response data. + dict: Resposta da consulta ou None em caso de falha. """ + endpoint = "services/pf/manterProgramacaoFinanceira" + + # Cria um cliente específico para o ano e endpoint da consulta + client = self._criar_cliente_soap(ano, endpoint) + if not client: + logger.error( + f"Não foi possível criar cliente SOAP ano {ano} e endpoint {endpoint}." + ) + return None + + soap_headers = { + "cabecalhoSIAFI": { + "nomeSistemaSIAFI": f"SIAFI{str(ano)}", + "ug": ug_emitente, + "bilhetador": {"nonce": "nonce123456"}, + } + } try: - response = self.client.get(url=endpoint) - response.raise_for_status() - data = response.json() - return data - except HTTPStatusError as http_err: - logging.error(f"HTTP error occurred: {http_err}") - except json.decoder.JSONDecodeError: - logging.warning(f"Could not decode response from {endpoint}") - return None + logger.info( + f"Consultando Programação Financeira: UG {ug_emitente}, Ano {ano}, " + f"Documento {num_lista}..." + ) + response = client.service.pfDetalharProgramacaoFinanceira( + _soapheaders=soap_headers, + ano=ano, + numeroDocumento=num_lista, + codUgEmit=ug_emitente, + ) + logger.info(f"Resposta recebida: {response}") + + response_dict: Dict[str, Any] = dict(response) if response else {} + return response_dict + except Exception as e: + logger.error(f"Erro na consulta: {e}") + return None From 2bca40fbbfb299afa9e913080a89f07d93795003 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Sun, 23 Mar 2025 18:52:58 -0300 Subject: [PATCH 027/317] fix(beneficiario): ajusta insercao dos beneficiarios --- .../programa_beneficiario_ingest_dag.py | 2 +- .../dags/data_ingest/programas_ingest_dag.py | 31 +++++++---- airflow_lappis/plugins/cliente_postgres.py | 55 ++++++++++++++++++- 3 files changed, 76 insertions(+), 12 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/programa_beneficiario_ingest_dag.py b/airflow_lappis/dags/data_ingest/programa_beneficiario_ingest_dag.py index a1fb771c..3fc00436 100644 --- a/airflow_lappis/dags/data_ingest/programa_beneficiario_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/programa_beneficiario_ingest_dag.py @@ -40,7 +40,7 @@ def fetch_and_store_programa_beneficiario() -> None: db.insert_data( unique_id_programas, - "beneficiario", + "programas", primary_key=["id_programa"], conflict_fields=["id_programa"], schema="transfere_gov", diff --git a/airflow_lappis/dags/data_ingest/programas_ingest_dag.py b/airflow_lappis/dags/data_ingest/programas_ingest_dag.py index 38249ed2..da12cebd 100644 --- a/airflow_lappis/dags/data_ingest/programas_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/programas_ingest_dag.py @@ -20,29 +20,40 @@ def api_programas_dag() -> None: @task - def fetch_and_store_programas() -> None: - logging.info("Starting api_programas_dag DAG") + def fetch_and_update_programas() -> None: + logging.info("Starting api_programas_dag - Update Programs") api = ClienteTed() postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) id_programas = db.get_id_programas() + total_processed = 0 for id_programa in id_programas: - programas = api.get_programa_by_id_programa(id_programa) - if programas: - logging.info("Inserting programas into PostgreSQL") + programas_data = api.get_programa_by_id_programa(id_programa) + if programas_data and len(programas_data) > 0: + programa = programas_data[0] + # Alter table to add any new columns needed + db.alter_table(programa, "programas", schema="transfere_gov") + + # Insert/update the program data db.insert_data( - programas, + programas_data, "programas", - primary_key=["tx_codigo_programa"], - conflict_fields=["tx_codigo_programa"], + primary_key=["id_programa"], + conflict_fields=["id_programa"], schema="transfere_gov", ) + + total_processed += 1 + if total_processed % 10 == 0: + logging.info(f"Processed {total_processed} programs") else: - logging.warning(f"No programas found for id_programas: {id_programas}") + logging.warning(f"No program data found for id_programa: {id_programa}") + + logging.info(f"Completed processing {total_processed} programs") - fetch_and_store_programas() + fetch_and_update_programas() dag_instance = api_programas_dag() diff --git a/airflow_lappis/plugins/cliente_postgres.py b/airflow_lappis/plugins/cliente_postgres.py index b8990f71..0f9fffa3 100755 --- a/airflow_lappis/plugins/cliente_postgres.py +++ b/airflow_lappis/plugins/cliente_postgres.py @@ -192,7 +192,7 @@ def get_contratos_ids(self) -> List[int]: def get_id_programas(self) -> List[int]: """Extrai todos os IDs de programas da tabela beneficiário.""" - query = "SELECT id_programa FROM transfere_gov.beneficiario" + query = "SELECT id_programa FROM transfere_gov.programas" with psycopg2.connect(self.conn_str) as conn: with conn.cursor() as cursor: @@ -254,3 +254,56 @@ def get_programacao_financeira(self) -> List[Tuple[Any, ...]]: cursor.execute(query) programacao_financeira = cursor.fetchall() return programacao_financeira + + def alter_table( + self, data: Dict[str, Any], table_name: str, schema: str = "raw" + ) -> None: + """ + Alter table to add columns that exist in the data but not in the table. + All new columns will be created as TEXT type. + + Args: + data: Sample data containing new columns + table_name: Name of table to alter + schema: Database schema name + """ + flattened_data = self._flatten_data([data])[0] + columns = list(flattened_data.keys()) + + with psycopg2.connect(self.conn_str) as conn: + with conn.cursor() as cursor: + # Get existing columns + cursor.execute( + f""" + SELECT column_name + FROM information_schema.columns + WHERE table_schema = '{schema}' + AND table_name = '{table_name}' + """ + ) + existing_columns = [row[0] for row in cursor.fetchall()] + + # Add columns that don't exist + for column in columns: + if column not in existing_columns: + alter_query = ( + f"ALTER TABLE {schema}.{table_name} " + f"ADD COLUMN IF NOT EXISTS {column} TEXT;" + ) + try: + cursor.execute(alter_query) + logging.info( + f"[cliente_postgres.py] Added column {column} " + f"to {schema}.{table_name}" + ) + except psycopg2.Error as e: + logging.error( + f"[cliente_postgres.py] Failed to add {column} " + f"to {schema}.{table_name}. Error: {str(e)}" + ) + + conn.commit() + logging.info( + f"[cliente_postgres.py] Table {schema}.{table_name} " + f"altered successfully" + ) From b1a9ecc267f211d8939f7c5c39e761ebec939a3d Mon Sep 17 00:00:00 2001 From: arthrok Date: Sun, 23 Mar 2025 20:03:47 -0300 Subject: [PATCH 028/317] =?UTF-8?q?fix(requirements):=20ajusta=20vers?= =?UTF-8?q?=C3=A3o=20das=20libs=20e=20retira=20libs=20padr=C3=B5es=20da=20?= =?UTF-8?q?imagem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 153 ++++------------------------------------------- 1 file changed, 12 insertions(+), 141 deletions(-) diff --git a/requirements.txt b/requirements.txt index 29a31feb..45a41110 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,39 +1,10 @@ aenum==3.1.15 ; python_version >= "3.11" and python_version < "3.12" +protobuf==5.29.3 ; python_version >= "3.11" and python_version < "3.12" agate==1.9.1 ; python_version >= "3.11" and python_version < "3.12" aiohappyeyeballs==2.5.0 ; python_version >= "3.11" and python_version < "3.12" -aiohttp==3.10.11 ; python_version >= "3.11" and python_version < "3.12" -aiosignal==1.3.2 ; python_version >= "3.11" and python_version < "3.12" -alembic==1.15.1 ; python_version >= "3.11" and python_version < "3.12" -annotated-types==0.7.0 ; python_version >= "3.11" and python_version < "3.12" -anyio==4.8.0 ; python_version >= "3.11" and python_version < "3.12" -apache-airflow-providers-common-io==1.4.2 ; python_version >= "3.11" and python_version < "3.12" -apache-airflow-providers-common-sql==1.20.0 ; python_version >= "3.11" and python_version < "3.12" -apache-airflow-providers-ftp==3.11.1 ; python_version >= "3.11" and python_version < "3.12" -apache-airflow-providers-http==4.13.3 ; python_version >= "3.11" and python_version < "3.12" -apache-airflow-providers-imap==3.7.0 ; python_version >= "3.11" and python_version < "3.12" -apache-airflow-providers-postgres==5.14.0 ; python_version >= "3.11" and python_version < "3.12" -apache-airflow-providers-sqlite==3.9.1 ; python_version >= "3.11" and python_version < "3.12" -apache-airflow==2.8.1 ; python_version >= "3.11" and python_version < "3.12" -apispec[yaml]==6.8.1 ; python_version >= "3.11" and python_version < "3.12" -argcomplete==3.6.0 ; python_version >= "3.11" and python_version < "3.12" -asgiref==3.8.1 ; python_version >= "3.11" and python_version < "3.12" +apispec[yaml]==6.4.0 ; python_version >= "3.11" and python_version < "3.12" astronomer-cosmos==1.9.0 ; python_version >= "3.11" and python_version < "3.12" -attrs==25.1.0 ; python_version >= "3.11" and python_version < "3.12" -babel==2.17.0 ; python_version >= "3.11" and python_version < "3.12" -blinker==1.9.0 ; python_version >= "3.11" and python_version < "3.12" -cachelib==0.13.0 ; python_version >= "3.11" and python_version < "3.12" -certifi==2025.1.31 ; python_version >= "3.11" and python_version < "3.12" -cffi==1.17.1 ; python_version >= "3.11" and python_version < "3.12" and platform_python_implementation != "PyPy" -charset-normalizer==3.4.1 ; python_version >= "3.11" and python_version < "3.12" -click==8.1.8 ; python_version >= "3.11" and python_version < "3.12" -clickclick==20.10.2 ; python_version >= "3.11" and python_version < "3.12" -colorama==0.4.6 ; python_version >= "3.11" and python_version < "3.12" -colorlog==4.8.0 ; python_version >= "3.11" and python_version < "3.12" -configupdater==3.2 ; python_version >= "3.11" and python_version < "3.12" -connexion[flask]==2.14.1 ; python_version >= "3.11" and python_version < "3.12" -cron-descriptor==1.4.5 ; python_version >= "3.11" and python_version < "3.12" -croniter==6.0.0 ; python_version >= "3.11" and python_version < "3.12" -cryptography==44.0.2 ; python_version >= "3.11" and python_version < "3.12" +connexion[flask]==2.14.2 ; python_version >= "3.11" and python_version < "3.12" daff==1.3.46 ; python_version >= "3.11" and python_version < "3.12" dbt-adapters==1.14.1 ; python_version >= "3.11" and python_version < "3.12" dbt-common==1.15.0 ; python_version >= "3.11" and python_version < "3.12" @@ -41,64 +12,6 @@ dbt-core==1.9.3 ; python_version >= "3.11" and python_version < "3.12" dbt-extractor==0.5.1 ; python_version >= "3.11" and python_version < "3.12" dbt-postgres==1.9.0 ; python_version >= "3.11" and python_version < "3.12" dbt-semantic-interfaces==0.7.4 ; python_version >= "3.11" and python_version < "3.12" -deepdiff==7.0.1 ; python_version >= "3.11" and python_version < "3.12" -deprecated==1.2.18 ; python_version >= "3.11" and python_version < "3.12" -deprecation==2.1.0 ; python_version >= "3.11" and python_version < "3.12" -dill==0.3.9 ; python_version >= "3.11" and python_version < "3.12" -distlib==0.3.9 ; python_version >= "3.11" and python_version < "3.12" -dnspython==2.7.0 ; python_version >= "3.11" and python_version < "3.12" -email-validator==1.3.1 ; python_version >= "3.11" and python_version < "3.12" -filelock==3.17.0 ; python_version >= "3.11" and python_version < "3.12" -flask-appbuilder==4.3.10 ; python_version >= "3.11" and python_version < "3.12" -flask-babel==2.0.0 ; python_version >= "3.11" and python_version < "3.12" -flask-caching==2.3.1 ; python_version >= "3.11" and python_version < "3.12" -flask-jwt-extended==4.7.1 ; python_version >= "3.11" and python_version < "3.12" -flask-limiter==3.10.1 ; python_version >= "3.11" and python_version < "3.12" -flask-login==0.6.3 ; python_version >= "3.11" and python_version < "3.12" -flask-session==0.5.0 ; python_version >= "3.11" and python_version < "3.12" -flask-sqlalchemy==2.5.1 ; python_version >= "3.11" and python_version < "3.12" -flask-wtf==1.2.2 ; python_version >= "3.11" and python_version < "3.12" -flask==2.2.5 ; python_version >= "3.11" and python_version < "3.12" -frozenlist==1.5.0 ; python_version >= "3.11" and python_version < "3.12" -fsspec==2025.3.0 ; python_version >= "3.11" and python_version < "3.12" -google-re2==1.1.20240702 ; python_version >= "3.11" and python_version < "3.12" -googleapis-common-protos==1.69.1 ; python_version >= "3.11" and python_version < "3.12" -greenlet==3.1.1 ; python_version >= "3.11" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32") and python_version < "3.12" -grpcio==1.71.0 ; python_version >= "3.11" and python_version < "3.12" -gunicorn==23.0.0 ; python_version >= "3.11" and python_version < "3.12" -h11==0.14.0 ; python_version >= "3.11" and python_version < "3.12" -httpcore==1.0.7 ; python_version >= "3.11" and python_version < "3.12" -httpx==0.28.1 ; python_version >= "3.11" and python_version < "3.12" -idna==3.10 ; python_version >= "3.11" and python_version < "3.12" -imap-tools==1.10.0 ; python_version >= "3.11" and python_version < "3.12" -importlib-metadata==6.11.0 ; python_version >= "3.11" and python_version < "3.12" -inflection==0.5.1 ; python_version >= "3.11" and python_version < "3.12" -isodate==0.6.1 ; python_version >= "3.11" and python_version < "3.12" -itsdangerous==2.2.0 ; python_version >= "3.11" and python_version < "3.12" -jinja2==3.1.6 ; python_version >= "3.11" and python_version < "3.12" -jsonschema-specifications==2024.10.1 ; python_version >= "3.11" and python_version < "3.12" -jsonschema==4.23.0 ; python_version >= "3.11" and python_version < "3.12" -lazy-object-proxy==1.10.0 ; python_version >= "3.11" and python_version < "3.12" -leather==0.4.0 ; python_version >= "3.11" and python_version < "3.12" -limits==4.1 ; python_version >= "3.11" and python_version < "3.12" -linkify-it-py==2.0.3 ; python_version >= "3.11" and python_version < "3.12" -lockfile==0.12.2 ; python_version >= "3.11" and python_version < "3.12" -lxml==5.3.1 ; python_version >= "3.11" and python_version < "3.12" -mako==1.3.9 ; python_version >= "3.11" and python_version < "3.12" -markdown-it-py==3.0.0 ; python_version >= "3.11" and python_version < "3.12" -markdown==3.7 ; python_version >= "3.11" and python_version < "3.12" -markupsafe==3.0.2 ; python_version >= "3.11" and python_version < "3.12" -marshmallow-oneofschema==3.1.1 ; python_version >= "3.11" and python_version < "3.12" -marshmallow-sqlalchemy==0.26.1 ; python_version >= "3.11" and python_version < "3.12" -marshmallow==3.26.1 ; python_version >= "3.11" and python_version < "3.12" -mashumaro[msgpack]==3.14 ; python_version >= "3.11" and python_version < "3.12" -mdit-py-plugins==0.4.2 ; python_version >= "3.11" and python_version < "3.12" -mdurl==0.1.2 ; python_version >= "3.11" and python_version < "3.12" -more-itertools==10.6.0 ; python_version >= "3.11" and python_version < "3.12" -msgpack==1.1.0 ; python_version >= "3.11" and python_version < "3.12" -multidict==6.1.0 ; python_version >= "3.11" and python_version < "3.12" -networkx==3.4.2 ; python_version >= "3.11" and python_version < "3.12" -numpy==1.26.4 ; python_version >= "3.11" and python_version < "3.12" opentelemetry-api==1.30.0 ; python_version >= "3.11" and python_version < "3.12" opentelemetry-exporter-otlp-proto-common==1.30.0 ; python_version >= "3.11" and python_version < "3.12" opentelemetry-exporter-otlp-proto-grpc==1.30.0 ; python_version >= "3.11" and python_version < "3.12" @@ -107,61 +20,19 @@ opentelemetry-exporter-otlp==1.30.0 ; python_version >= "3.11" and python_versio opentelemetry-proto==1.30.0 ; python_version >= "3.11" and python_version < "3.12" opentelemetry-sdk==1.30.0 ; python_version >= "3.11" and python_version < "3.12" opentelemetry-semantic-conventions==0.51b0 ; python_version >= "3.11" and python_version < "3.12" -ordered-set==4.1.0 ; python_version >= "3.11" and python_version < "3.12" -packaging==24.2 ; python_version >= "3.11" and python_version < "3.12" -pandas==2.2.3 ; python_version >= "3.11" and python_version < "3.12" +deepdiff==7.0.1 ; python_version >= "3.11" and python_version < "3.12" +deprecation==2.1.0 ; python_version >= "3.11" and python_version < "3.12" +imap-tools==1.10.0 ; python_version >= "3.11" and python_version < "3.12" +leather==0.4.0 ; python_version >= "3.11" and python_version < "3.12" +mashumaro[msgpack]==3.14 ; python_version >= "3.11" and python_version < "3.12" +more-itertools==10.6.0 ; python_version >= "3.11" and python_version < "3.12" +msgpack==1.1.0 ; python_version >= "3.11" and python_version < "3.12" +networkx==3.4.2 ; python_version >= "3.11" and python_version < "3.12" parsedatetime==2.6 ; python_version >= "3.11" and python_version < "3.12" -pathspec==0.12.1 ; python_version >= "3.11" and python_version < "3.12" -pendulum==3.0.0 ; python_version >= "3.11" and python_version < "3.12" -platformdirs==4.3.6 ; python_version >= "3.11" and python_version < "3.12" -pluggy==1.5.0 ; python_version >= "3.11" and python_version < "3.12" -prison==0.2.1 ; python_version >= "3.11" and python_version < "3.12" propcache==0.3.0 ; python_version >= "3.11" and python_version < "3.12" -protobuf==5.29.3 ; python_version >= "3.11" and python_version < "3.12" -psutil==7.0.0 ; python_version >= "3.11" and python_version < "3.12" -psycopg2-binary==2.9.10 ; python_version >= "3.11" and python_version < "3.12" -pycparser==2.22 ; python_version >= "3.11" and python_version < "3.12" and platform_python_implementation != "PyPy" pydantic-core==2.27.2 ; python_version >= "3.11" and python_version < "3.12" -pydantic==2.10.6 ; python_version >= "3.11" and python_version < "3.12" -pygments==2.19.1 ; python_version >= "3.11" and python_version < "3.12" -pyjwt==2.10.1 ; python_version >= "3.11" and python_version < "3.12" -python-daemon==3.1.2 ; python_version >= "3.11" and python_version < "3.12" -python-dateutil==2.9.0.post0 ; python_version >= "3.11" and python_version < "3.12" -python-nvd3==0.16.0 ; python_version >= "3.11" and python_version < "3.12" -python-slugify==8.0.4 ; python_version >= "3.11" and python_version < "3.12" pytimeparse==1.1.8 ; python_version >= "3.11" and python_version < "3.12" -pytz==2025.1 ; python_version >= "3.11" and python_version < "3.12" -pyyaml==6.0.2 ; python_version >= "3.11" and python_version < "3.12" -referencing==0.36.2 ; python_version >= "3.11" and python_version < "3.12" requests-file==2.1.0 ; python_version >= "3.11" and python_version < "3.12" -requests-toolbelt==1.0.0 ; python_version >= "3.11" and python_version < "3.12" -requests==2.32.3 ; python_version >= "3.11" and python_version < "3.12" -rfc3339-validator==0.1.4 ; python_version >= "3.11" and python_version < "3.12" -rich-argparse==1.7.0 ; python_version >= "3.11" and python_version < "3.12" -rich==13.9.4 ; python_version >= "3.11" and python_version < "3.12" -rpds-py==0.23.1 ; python_version >= "3.11" and python_version < "3.12" -setproctitle==1.3.5 ; python_version >= "3.11" and python_version < "3.12" -six==1.17.0 ; python_version >= "3.11" and python_version < "3.12" -sniffio==1.3.1 ; python_version >= "3.11" and python_version < "3.12" snowplow-tracker==1.1.0 ; python_version >= "3.11" and python_version < "3.12" -sqlalchemy-jsonfield==1.0.2 ; python_version >= "3.11" and python_version < "3.12" -sqlalchemy-utils==0.41.2 ; python_version >= "3.11" and python_version < "3.12" -sqlalchemy==1.4.54 ; python_version >= "3.11" and python_version < "3.12" -sqlparse==0.5.3 ; python_version >= "3.11" and python_version < "3.12" -tabulate==0.9.0 ; python_version >= "3.11" and python_version < "3.12" -tenacity==9.0.0 ; python_version >= "3.11" and python_version < "3.12" -termcolor==2.5.0 ; python_version >= "3.11" and python_version < "3.12" -text-unidecode==1.3 ; python_version >= "3.11" and python_version < "3.12" typing-extensions==4.12.2 ; python_version >= "3.11" and python_version < "3.12" -tzdata==2025.1 ; python_version >= "3.11" and python_version < "3.12" -uc-micro-py==1.0.3 ; python_version >= "3.11" and python_version < "3.12" -unicodecsv==0.14.1 ; python_version >= "3.11" and python_version < "3.12" -universal-pathlib==0.2.6 ; python_version >= "3.11" and python_version < "3.12" -urllib3==2.3.0 ; python_version >= "3.11" and python_version < "3.12" -virtualenv==20.29.3 ; python_version >= "3.11" and python_version < "3.12" -werkzeug==2.3.8 ; python_version >= "3.11" and python_version < "3.12" -wrapt==1.17.2 ; python_version >= "3.11" and python_version < "3.12" -wtforms==3.2.1 ; python_version >= "3.11" and python_version < "3.12" -yarl==1.18.3 ; python_version >= "3.11" and python_version < "3.12" -zeep==4.3.1 ; python_version >= "3.11" and python_version < "3.12" -zipp==3.21.0 ; python_version >= "3.11" and python_version < "3.12" +zeep==4.3.1 ; python_version >= "3.11" and python_version < "3.12" \ No newline at end of file From 573f1d16e4c6b26e4d8faee9ba2ad9ee15c1cc9b Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Mon, 24 Mar 2025 01:55:55 +0000 Subject: [PATCH 029/317] Siafi/nota empenho --- .../nota_empenho_siafi_ingest_dag.py | 48 +++++++++++ airflow_lappis/helpers/retry_helpers.py | 80 +++++++++++++++++++ airflow_lappis/plugins/cliente_siafi.py | 56 +++++++++++++ 3 files changed, 184 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/nota_empenho_siafi_ingest_dag.py create mode 100644 airflow_lappis/helpers/retry_helpers.py diff --git a/airflow_lappis/dags/data_ingest/nota_empenho_siafi_ingest_dag.py b/airflow_lappis/dags/data_ingest/nota_empenho_siafi_ingest_dag.py new file mode 100644 index 00000000..3a92135a --- /dev/null +++ b/airflow_lappis/dags/data_ingest/nota_empenho_siafi_ingest_dag.py @@ -0,0 +1,48 @@ +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from cliente_siafi import ClienteSiafi +from cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 3, 17), + catchup=False, + default_args={ + "owner": "Davi", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["nota_empenho", "siafi_api"], +) +def nota_empenho_siafi_ingest_dag() -> None: + @task + def fetch_and_store_notas_empenho() -> None: + cliente = ClienteSiafi() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + ugs_emitentes = ["113601", "113602"] + ano_atual = datetime.now().year + + for ug in ugs_emitentes: + for ano in range(2023, ano_atual + 1): + num_empenho = 1 + while True: + num_empenho_str = str(num_empenho).zfill(6) + resultado = cliente.consultar_nota_empenho(ug, ano, num_empenho_str) + if not resultado: + break + db.insert_data( + [resultado], + "notas_empenho", + conflict_fields=["numEmpenho", "anoEmpenho"], + primary_key=["numEmpenho", "anoEmpenho"], + schema="siafi", + ) + num_empenho += 1 + + fetch_and_store_notas_empenho() + + +dag_instance = nota_empenho_siafi_ingest_dag() diff --git a/airflow_lappis/helpers/retry_helpers.py b/airflow_lappis/helpers/retry_helpers.py new file mode 100644 index 00000000..b3e20fb5 --- /dev/null +++ b/airflow_lappis/helpers/retry_helpers.py @@ -0,0 +1,80 @@ +import logging +import time +import functools +from typing import Callable, TypeVar, Any, cast, Tuple + +# Configuração do logger +logger = logging.getLogger(__name__) + +# Definindo um tipo genérico para o decorador +T = TypeVar("T") + + +def retry_on_exception( + max_attempts: int = 3, + initial_delay: float = 1.0, + backoff_factor: float = 2.0, + exceptions_to_retry: Tuple = (Exception,), +) -> Callable[[Callable[..., T]], Callable[..., T]]: + """ + Decorador que implementa um mecanismo de retry para funções que podem falhar. + + Args: + max_attempts (int): Número máximo de tentativas + initial_delay (float): Tempo inicial de espera entre tentativas em segundos + backoff_factor (float): Multiplicador do tempo de espera para cada nova tentativa + exceptions_to_retry (tuple): Exceções que devem ativar o retry + + Returns: + Callable: Função decorada com mecanismo de retry + """ + + def decorator(func: Callable[..., T]) -> Callable[..., T]: + @functools.wraps(func) + def wrapper(*args: Any, **kwargs: Any) -> T: + last_exception = None + delay = initial_delay + + # Extrai informações para logging mais claro + method_name = func.__name__ + + for attempt in range(1, max_attempts + 1): + try: + if attempt > 1: + logger.info( + f"Tentativa {attempt}/{max_attempts} para {method_name}" + ) + + return func(*args, **kwargs) + + except exceptions_to_retry as e: + last_exception = e + + if attempt < max_attempts: + + error_msg = ( + f"Tentativa {attempt} falhou para {method_name}: {str(e)}" + ) + logger.warning(error_msg) + + logger.info( + f"Aguardando {delay:.2f}s antes da próxima tentativa..." + ) + time.sleep(delay) + # Aumenta o delay para a próxima tentativa (backoff exponencial) + delay *= backoff_factor + else: + logger.error( + f"Todas as {max_attempts} tentativas falharam: {method_name}" + ) + + # Se chegou aqui, todas as tentativas falharam + if last_exception: + raise last_exception + + # Este ponto nunca deveria ser alcançado, mas é necessário para tipagem + raise RuntimeError("Erro inesperado no mecanismo de retry") + + return cast(Callable[..., T], wrapper) + + return decorator diff --git a/airflow_lappis/plugins/cliente_siafi.py b/airflow_lappis/plugins/cliente_siafi.py index 6715369f..41730ef7 100644 --- a/airflow_lappis/plugins/cliente_siafi.py +++ b/airflow_lappis/plugins/cliente_siafi.py @@ -5,6 +5,7 @@ from zeep.wsse.username import UsernameToken from requests import Session from typing import Dict, Any, Optional +from retry_helpers import retry_on_exception # Configuração do logger logging.basicConfig( @@ -62,6 +63,7 @@ def _criar_cliente_soap(self, ano: int, endpoint: str) -> Optional[Client]: ) return None + @retry_on_exception(max_attempts=3, initial_delay=2.0, backoff_factor=2.0) def consultar_programacao_financeira( self, ug_emitente: str, ano: int, num_lista: str ) -> Optional[Dict[str, Any]]: @@ -112,3 +114,57 @@ def consultar_programacao_financeira( except Exception as e: logger.error(f"Erro na consulta: {e}") return None + + @retry_on_exception(max_attempts=3, initial_delay=2.0, backoff_factor=2.0) + def consultar_nota_empenho( + self, ug_emitente: str, ano_empenho: int, num_empenho: str + ) -> Optional[Dict[str, Any]]: + """ + Consulta detalhes de uma Nota de Empenho no SIAFI. + + Args: + ug_emitente (str): UG emitente da Nota de Empenho. + ano_empenho (int): Ano da Nota de Empenho. + num_empenho (str): Número da Nota de Empenho. + + Returns: + dict: Resposta da consulta ou None em caso de falha. + """ + endpoint = "services/orcamentario/manterOrcamentario" + client = self._criar_cliente_soap(ano_empenho, endpoint) + if not client: + logger.error( + f"Não foi possível criar cliente ano {ano_empenho} e endpoint {endpoint}." + ) + return None + + soap_headers = { + "cabecalhoSIAFI": { + "nomeSistemaSIAFI": f"SIAFI{str(ano_empenho)}", + "ug": ug_emitente, + "bilhetador": {"nonce": "nonce123456"}, + } + } + + parametros_consulta = { + "ugEmitente": ug_emitente, + "anoEmpenho": ano_empenho, + "numEmpenho": num_empenho.zfill(6), + } + + try: + logger.info( + f"Consultando Nota de Empenho {parametros_consulta['numEmpenho']}..." + ) + response = client.service.orcDetalharEmpenho( + parametros_consulta, _soapheaders=soap_headers + ) + logger.info(f"Resposta recebida: {response}") + + response_dict: Dict[str, Any] = dict(response) if response else {} + return response_dict + except Exception as e: + logger.error( + f"Erro consulta da Nota Empenho {parametros_consulta['numEmpenho']}: {e}" + ) + return None From 67aacff68352db2c8280c9941127b3e5d6c44852 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Sun, 23 Mar 2025 23:41:15 -0300 Subject: [PATCH 030/317] feat(dag): adiciona dag de insercao dos planos de acao --- .../dags/data_ingest/plano_acao_ingest_dag.py | 54 +++++++++++++++++++ airflow_lappis/plugins/cliente_ted.py | 22 ++++++++ 2 files changed, 76 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/plano_acao_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/plano_acao_ingest_dag.py b/airflow_lappis/dags/data_ingest/plano_acao_ingest_dag.py new file mode 100644 index 00000000..a9674fc7 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/plano_acao_ingest_dag.py @@ -0,0 +1,54 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from postgres_helpers import get_postgres_conn +from cliente_ted import ClienteTed +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Davi", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["ted_api", "planos_acao"], +) +def api_planos_acao_dag() -> None: + + @task + def fetch_and_store_planos_acao() -> None: + logging.info("Starting api_planos_acao_dag DAG") + api = ClienteTed() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + id_programas = db.get_id_programas() + + total_processed = 0 + for id_programa in id_programas: + planos_acao_data = api.get_planos_acao_by_id_programa(id_programa) + if planos_acao_data: + + db.insert_data( + planos_acao_data, + "planos_acao", + primary_key=["id_plano_acao"], + conflict_fields=["id_plano_acao"], + schema="transfere_gov", + ) + + total_processed += 1 + if total_processed % 10 == 0: + logging.info(f"Processed {total_processed} planos de ação") + else: + logging.warning(f"No planos de ação found for id_programa: {id_programa}") + + logging.info(f"Completed processing {total_processed} planos de ação") + + fetch_and_store_planos_acao() + + +dag_instance = api_planos_acao_dag() diff --git a/airflow_lappis/plugins/cliente_ted.py b/airflow_lappis/plugins/cliente_ted.py index d7bafe2d..fcc5e28b 100644 --- a/airflow_lappis/plugins/cliente_ted.py +++ b/airflow_lappis/plugins/cliente_ted.py @@ -111,3 +111,25 @@ def get_programacao_financeira_by_ug(self, ug_code: int) -> list | None: data = data_1 + data_2 return data if data else None + + def get_planos_acao_by_id_programa(self, id_programa: str) -> list | None: + + endpoint = f"plano_acao?id_programa=eq.{id_programa}" + logging.info( + f"[cliente_ted.py] Fetching planos de ação for id_programa: {id_programa}" + ) + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER + ) + if status == http.HTTPStatus.OK and isinstance(data, list): + logging.info( + "[cliente_ted.py] Successfully fetched planos de ação for id_programa: " + f"{id_programa}" + ) + return data + else: + logging.warning( + "[cliente_ted.py] Failed to fetch planos de ação for id_programa: " + f"{id_programa} with status: {status}" + ) + return None From ef39d09c5cfa8be89aca9a2db77f341375d19147 Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Thu, 27 Mar 2025 18:00:46 +0000 Subject: [PATCH 031/317] feat(dag): adiciona dag de ingestao das notas de creditos do siafi --- .../nota_credito_siafi_ingest_dag.py | 53 +++++++ airflow_lappis/plugins/cliente_postgres.py | 17 +++ airflow_lappis/plugins/cliente_siafi.py | 129 +++++++++++++++++- 3 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 airflow_lappis/dags/data_ingest/nota_credito_siafi_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/nota_credito_siafi_ingest_dag.py b/airflow_lappis/dags/data_ingest/nota_credito_siafi_ingest_dag.py new file mode 100644 index 00000000..8b2d7d9a --- /dev/null +++ b/airflow_lappis/dags/data_ingest/nota_credito_siafi_ingest_dag.py @@ -0,0 +1,53 @@ +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from cliente_siafi import ClienteSiafi +from cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn + + +@dag( + schedule_interval="@daily", + start_date=datetime(2024, 3, 12), + catchup=False, + default_args={ + "owner": "Davi", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["nota_credito", "siafi_api"], +) +def nota_credito_siafi_dag() -> None: + @task + def fetch_and_store_nota_credito() -> None: + cliente_siafi = ClienteSiafi() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + notas_credito = db.get_nota_credito() + + for nota_credito in notas_credito: + cd_ug_emitente_nota = nota_credito[0] + cd_gestao_emitente_nota = nota_credito[1] + tx_numero_nota = nota_credito[2] + ano = tx_numero_nota[:4] + numero = tx_numero_nota[-6:] + + response = cliente_siafi.consultar_nota_credito( + ug=cd_ug_emitente_nota, + gestao=cd_gestao_emitente_nota, + ano=ano, + numero=numero, + ) + if response: + response["ano"] = ano + db.insert_data( + [response], + "nota_credito", + conflict_fields=["numero", "ano"], + primary_key=["numero", "ano"], + schema="siafi", + ) + + fetch_and_store_nota_credito() + + +dag_instance = nota_credito_siafi_dag() diff --git a/airflow_lappis/plugins/cliente_postgres.py b/airflow_lappis/plugins/cliente_postgres.py index 0f9fffa3..ca5e7f6b 100755 --- a/airflow_lappis/plugins/cliente_postgres.py +++ b/airflow_lappis/plugins/cliente_postgres.py @@ -307,3 +307,20 @@ def alter_table( f"[cliente_postgres.py] Table {schema}.{table_name} " f"altered successfully" ) + + def get_nota_credito(self) -> List[Tuple[Any, ...]]: + """Extrai o número da nota de crédito e o valor da tabela nota_credito. + + Returns: + List[Tuple[Any, ...]]: Lista de tuplas com número da nota de crédito e valor + """ + query = ( + "SELECT cd_ug_emitente_nota, cd_gestao_emitente_nota, tx_numero_nota " + "FROM transfere_gov.notas_de_credito" + ) + + with psycopg2.connect(self.conn_str) as conn: + with conn.cursor() as cursor: + cursor.execute(query) + nota_credito = cursor.fetchall() + return nota_credito diff --git a/airflow_lappis/plugins/cliente_siafi.py b/airflow_lappis/plugins/cliente_siafi.py index 41730ef7..654dfe69 100644 --- a/airflow_lappis/plugins/cliente_siafi.py +++ b/airflow_lappis/plugins/cliente_siafi.py @@ -6,6 +6,8 @@ from requests import Session from typing import Dict, Any, Optional from retry_helpers import retry_on_exception +import requests +import base64 # Configuração do logger logging.basicConfig( @@ -20,8 +22,8 @@ def __init__(self) -> None: Inicializa o cliente SIAFI com as configurações necessárias. """ self.base_url = "https://servicos-siafi.tesouro.gov.br/siafi" - self.cert_path = os.getenv("SIAFI_CERT_PATH") - self.key_path = os.getenv("SIAFI_KEY_PATH") + self.cert_path = os.getenv("SIAFI_CERT") + self.key_path = os.getenv("SIAFI_KEY") self.siafi_username = os.getenv("SIAFI_USERNAME") self.siafi_password = os.getenv("SIAFI_PASSWORD") @@ -168,3 +170,126 @@ def consultar_nota_empenho( f"Erro consulta da Nota Empenho {parametros_consulta['numEmpenho']}: {e}" ) return None + + @retry_on_exception(max_attempts=3, initial_delay=2.0, backoff_factor=2.0) + def get_access_token(self) -> Optional[str]: + """ + Obtém um token de acesso usando autenticação HTTP Basic. + + Returns: + str: Token de acesso ou None em caso de falha. + """ + # Credenciais + consumer_key = os.getenv("SIAFI_BEARER_KEY_SERPRO") + consumer_secret = os.getenv("SIAFI_BEARER_SECRET_SERPRO") + + if not consumer_key or not consumer_secret: + logger.error("Credenciais de autenticação não configuradas.") + return None + + # Codificar as credenciais em Base64 + credentials = f"{consumer_key}:{consumer_secret}" + encoded_credentials = base64.b64encode(credentials.encode("utf-8")).decode( + "utf-8" + ) + + # URL para obter o token + url = "https://gateway.apiserpro.serpro.gov.br/token" + + # Parâmetros de dados + data = {"grant_type": "client_credentials"} + + # Cabeçalhos com a autorização codificada em Base64 + headers = { + "Authorization": f"Basic {encoded_credentials}", + "Content-Type": "application/x-www-form-urlencoded", + } + + try: + # Realizar a requisição POST para obter o token + response = requests.post(url, data=data, headers=headers) + + if response.status_code == 200: + token = response.json().get("access_token") + if isinstance(token, str): + logger.info("Token obtido com sucesso.") + return token + else: + logger.error("Resposta não contém o campo 'access_token'.") + return None + else: + logger.error( + f"Erro ao obter o token: {response.status_code}, {response.text}" + ) + return None + except Exception as e: + logger.error(f"Erro na requisição para obter o token: {e}") + return None + + @retry_on_exception(max_attempts=3, initial_delay=2.0, backoff_factor=2.0) + def consultar_nota_credito( + self, ug: str, gestao: str, ano: str, numero: str + ) -> Dict[str, Any]: + """ + Consulta a nota de crédito na API SIAFI. + + Args: + ug (str): Unidade Gestora. + gestao (str): Código da gestão. + ano (str): Ano da nota. + numero (str): Número da nota. + + Returns: + dict: JSON da resposta ou erro. + """ + + cpf = os.getenv("SIAFI_CPF_SERPRO") + ug_orgao = "113601" + base_credential = f"{cpf}.{ug_orgao}.SIAFI" + # Obter o x-credencial baseado no ano + x_credencial = f"{base_credential}{ano}" + encoded_x_credencial = base64.b64encode(x_credencial.encode("utf-8")).decode( + "utf-8" + ) + + # URL da requisição + BASE_URL = "https://gateway.apiserpro.serpro.gov.br/api-integra-siafi/api" + url = f"{BASE_URL}/v2/nota-credito/{ug}/{gestao}/{ano}/{numero}" + + # Obtém o token de acesso + token = self.get_access_token() + if not token: + logger.error("Não foi possível obter o token de acesso.") + return {"error": "Falha ao obter o token de acesso."} + + # Configura os cabeçalhos da requisição + headers = { + "accept": "application/json", + "x-credencial": encoded_x_credencial, + "Authorization": f"Bearer {token}", + } + + try: + # Faz a requisição GET + logger.info( + f"Consultando NC: UG={ug} Gestão={gestao} Ano={ano} Número={numero}" + ) + response = requests.get(url, headers=headers) + + # Verifica o status da resposta + if response.status_code == 200: + logger.info("Consulta realizada com sucesso.") + response_json = response.json() + if isinstance(response_json, dict): + return response_json + else: + logger.error("Resposta não é um JSON válido.") + return {"error": "Resposta inválida."} + else: + logger.error( + f"Erro na consulta: {response.status_code} - {response.text}" + ) + return {"error": f"Erro {response.status_code}: {response.text}"} + except Exception as e: + logger.error(f"Erro ao consultar a nota de crédito: {e}") + return {"error": f"Erro na requisição: {e}"} From d5f188134d261cbcd3ac4c7e246ff5fe2b38954e Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Fri, 28 Mar 2025 10:13:55 +0000 Subject: [PATCH 032/317] feat(cliente): ajusta retorno das notas de credito --- airflow_lappis/plugins/cliente_siafi.py | 63 ++++++++++++++++++------- 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/airflow_lappis/plugins/cliente_siafi.py b/airflow_lappis/plugins/cliente_siafi.py index 654dfe69..fa73eb28 100644 --- a/airflow_lappis/plugins/cliente_siafi.py +++ b/airflow_lappis/plugins/cliente_siafi.py @@ -43,7 +43,9 @@ def _criar_cliente_soap(self, ano: int, endpoint: str) -> Optional[Client]: logger.info(f"Criando cliente SOAP com URL: {wsdl_url}") if not isinstance(self.cert_path, str) or not isinstance(self.key_path, str): - logger.error("Certificados SSL inválidos.") + logger.error( + f"Certificados inválidos. cert={self.cert_path}, key={self.key_path}" + ) return None session = Session() @@ -59,11 +61,21 @@ def _criar_cliente_soap(self, ano: int, endpoint: str) -> Optional[Client]: f"Cliente SOAP para o ano {ano} e endpoint {endpoint} criado com sucesso." ) return client + except requests.exceptions.SSLError as ssl_error: + logger.error( + f"Erro de SSL ao criar cliente SOAP. Verifique os certificados. " + f"cert_path={self.cert_path}, key_path={self.key_path}, erro={ssl_error}" + ) + except requests.exceptions.ConnectionError as conn_error: + logger.error( + f"Erro ao criar cliente SOAP. wsdl_url={wsdl_url}, erro={conn_error}" + ) except Exception as e: logger.error( - f"Erro ao criar o cliente SOAP para ano {ano} e endpoint {endpoint}: {e}" + f"Erro ao criar cliente SOAP para ano {ano} e endpoint {endpoint}. " + f"wsdl_url={wsdl_url}, erro={e}" ) - return None + return None @retry_on_exception(max_attempts=3, initial_delay=2.0, backoff_factor=2.0) def consultar_programacao_financeira( @@ -82,11 +94,11 @@ def consultar_programacao_financeira( """ endpoint = "services/pf/manterProgramacaoFinanceira" - # Cria um cliente específico para o ano e endpoint da consulta client = self._criar_cliente_soap(ano, endpoint) if not client: logger.error( - f"Não foi possível criar cliente SOAP ano {ano} e endpoint {endpoint}." + f"Falha ao criar cliente SOAP para consultar programação financeira. " + f"ug={ug_emitente}, ano={ano}, num_lista={num_lista}, endpoint={endpoint}" ) return None @@ -100,8 +112,7 @@ def consultar_programacao_financeira( try: logger.info( - f"Consultando Programação Financeira: UG {ug_emitente}, Ano {ano}, " - f"Documento {num_lista}..." + f"Consultando PF: UG={ug_emitente}, Ano={ano}, Documento={num_lista}" ) response = client.service.pfDetalharProgramacaoFinanceira( _soapheaders=soap_headers, @@ -113,9 +124,17 @@ def consultar_programacao_financeira( response_dict: Dict[str, Any] = dict(response) if response else {} return response_dict + except requests.exceptions.Timeout as timeout_error: + logger.error( + f"Timeout ao consultar programação financeira. " + f"ug={ug_emitente}, ano={ano}, num_list={num_lista}, erro={timeout_error}" + ) except Exception as e: - logger.error(f"Erro na consulta: {e}") - return None + logger.error( + f"Erro inesperado ao consultar programação financeira. " + f"ug_emitente={ug_emitente}, ano={ano}, num_lista={num_lista}, erro={e}" + ) + return None @retry_on_exception(max_attempts=3, initial_delay=2.0, backoff_factor=2.0) def consultar_nota_empenho( @@ -136,7 +155,9 @@ def consultar_nota_empenho( client = self._criar_cliente_soap(ano_empenho, endpoint) if not client: logger.error( - f"Não foi possível criar cliente ano {ano_empenho} e endpoint {endpoint}." + f"Falha ao criar cliente SOAP para consultar nota de empenho. " + f"ug={ug_emitente}, ano={ano_empenho}, " + f"numero={num_empenho}, endpoint={endpoint}" ) return None @@ -156,7 +177,7 @@ def consultar_nota_empenho( try: logger.info( - f"Consultando Nota de Empenho {parametros_consulta['numEmpenho']}..." + f"Consultando NE: UG={ug_emitente}, Ano={ano_empenho}, Num={num_empenho}" ) response = client.service.orcDetalharEmpenho( parametros_consulta, _soapheaders=soap_headers @@ -165,11 +186,17 @@ def consultar_nota_empenho( response_dict: Dict[str, Any] = dict(response) if response else {} return response_dict + except requests.exceptions.Timeout as te: + logger.error( + f"Timeout ao consultar nota de empenho. " + f"ug={ug_emitente}, ano={ano_empenho}, num={num_empenho}, erro={te}" + ) except Exception as e: logger.error( - f"Erro consulta da Nota Empenho {parametros_consulta['numEmpenho']}: {e}" + f"Erro inesperado ao consultar nota de empenho. " + f"ug={ug_emitente}, ano={ano_empenho}, num={num_empenho}, erro={e}" ) - return None + return None @retry_on_exception(max_attempts=3, initial_delay=2.0, backoff_factor=2.0) def get_access_token(self) -> Optional[str]: @@ -229,7 +256,7 @@ def get_access_token(self) -> Optional[str]: @retry_on_exception(max_attempts=3, initial_delay=2.0, backoff_factor=2.0) def consultar_nota_credito( self, ug: str, gestao: str, ano: str, numero: str - ) -> Dict[str, Any]: + ) -> Optional[Dict[str, Any]]: """ Consulta a nota de crédito na API SIAFI. @@ -240,7 +267,7 @@ def consultar_nota_credito( numero (str): Número da nota. Returns: - dict: JSON da resposta ou erro. + Optional[dict]: JSON da resposta ou None em caso de erro. """ cpf = os.getenv("SIAFI_CPF_SERPRO") @@ -284,12 +311,12 @@ def consultar_nota_credito( return response_json else: logger.error("Resposta não é um JSON válido.") - return {"error": "Resposta inválida."} + return None else: logger.error( f"Erro na consulta: {response.status_code} - {response.text}" ) - return {"error": f"Erro {response.status_code}: {response.text}"} + return None except Exception as e: logger.error(f"Erro ao consultar a nota de crédito: {e}") - return {"error": f"Erro na requisição: {e}"} + return None From 4dd0cde03746596c74b2d5e66a1b9c1ddd53531b Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Fri, 28 Mar 2025 11:05:31 -0300 Subject: [PATCH 033/317] fix(null): trata os nulos nas notas de creditos --- .../dags/data_ingest/nota_credito_siafi_ingest_dag.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/airflow_lappis/dags/data_ingest/nota_credito_siafi_ingest_dag.py b/airflow_lappis/dags/data_ingest/nota_credito_siafi_ingest_dag.py index 8b2d7d9a..ba5408b4 100644 --- a/airflow_lappis/dags/data_ingest/nota_credito_siafi_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/nota_credito_siafi_ingest_dag.py @@ -28,6 +28,10 @@ def fetch_and_store_nota_credito() -> None: cd_ug_emitente_nota = nota_credito[0] cd_gestao_emitente_nota = nota_credito[1] tx_numero_nota = nota_credito[2] + + if not all([cd_ug_emitente_nota, cd_gestao_emitente_nota, tx_numero_nota]): + continue + ano = tx_numero_nota[:4] numero = tx_numero_nota[-6:] From dbd3e3fb87f6c5ab21a8e5be0bded929f16a9c2d Mon Sep 17 00:00:00 2001 From: Joyce Dionizio Date: Sat, 29 Mar 2025 14:22:00 +0000 Subject: [PATCH 034/317] Feat/extract multi ug --- airflow_lappis/configs/orgaos.yaml | 16 +++++++ .../contratos_inativos_ingest_dag.py | 31 ++++++++++--- .../dags/data_ingest/contratos_ingest_dag.py | 43 +++++++++++++------ .../nota_empenho_siafi_ingest_dag.py | 25 ++++++++++- .../notas_de_credito_ingest_dag.py | 23 +++++++++- .../programacao_financeira_ingest_dag.py | 23 +++++++++- docker-compose.yml | 1 + 7 files changed, 140 insertions(+), 22 deletions(-) create mode 100644 airflow_lappis/configs/orgaos.yaml diff --git a/airflow_lappis/configs/orgaos.yaml b/airflow_lappis/configs/orgaos.yaml new file mode 100644 index 00000000..da253161 --- /dev/null +++ b/airflow_lappis/configs/orgaos.yaml @@ -0,0 +1,16 @@ +orgaos: + ipea: + codigos_ug: + - 113601 + - 113602 + unb: + codigos_ug: + - 154040 + ibama: + codigos_ug: + - 440001 + - 440048 + - 440050 + mgi: + codigos_ug: + - 201082 \ No newline at end of file diff --git a/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py b/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py index 7038c73b..03a23834 100755 --- a/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py @@ -1,5 +1,8 @@ import logging +import os +import yaml from airflow.decorators import dag, task +from airflow.models import Variable from datetime import datetime, timedelta from postgres_helpers import get_postgres_conn from cliente_contratos import ClienteContratos @@ -22,18 +25,36 @@ def api_contratos_inativos_dag() -> None: @task def fetch_and_store_contratos_inativos() -> None: - logging.info("Starting fetch_and_store_contratos task") + logging.info("Starting fetch_and_store_contratos_inativos task") + + orgao_alvo = Variable.get("ORGAO_ALVO", default_var=None) + if not orgao_alvo: + logging.error("Variável ORGAO_ALVO não definida no Airflow!") + raise ValueError("ORGAO_ALVO não definida no Airflow") + + config_path = os.path.join( + os.environ.get("AIRFLOW_HOME", "/opt/airflow"), "configs/orgaos.yaml" + ) + with open(config_path, "r") as f: + config = yaml.safe_load(f) + + orgaos = config.get("orgaos", {}) + ug_codes = orgaos.get(orgao_alvo, {}).get("codigos_ug", []) + + if not ug_codes: + logging.warning(f"Nenhum código UG encontrado para o órgão '{orgao_alvo}'") + return + api = ClienteContratos() postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) - ug_codes = [113601, 113602] for ug_code in ug_codes: - logging.info(f"Fetching contratos for UG code: {ug_code}") + logging.info(f"Fetching contratos inativos for UG code: {ug_code}") contratos = api.get_contratos_inativos_by_ug(ug_code) if contratos: logging.info( - f"Inserting contratos for UG code: " f"{ug_code} into PostgreSQL" + f"Inserting contratos inativos for UG code: {ug_code} into PostgreSQL" ) db.insert_data( contratos, @@ -43,7 +64,7 @@ def fetch_and_store_contratos_inativos() -> None: schema="compras_gov", ) else: - logging.warning(f"No contratos found for UG code: {ug_code}") + logging.warning(f"No contratos inativos found for UG code: {ug_code}") fetch_and_store_contratos_inativos() diff --git a/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py b/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py index e781fefa..7cb1ce95 100755 --- a/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py @@ -1,6 +1,9 @@ import logging +import os +import yaml from airflow.decorators import dag, task from airflow.operators.trigger_dagrun import TriggerDagRunOperator +from airflow.models import Variable from datetime import datetime, timedelta from postgres_helpers import get_postgres_conn from cliente_contratos import ClienteContratos @@ -19,26 +22,40 @@ tags=["contratos_api"], ) def api_contratos_dag() -> None: - """DAG para buscar e armazenar contratos de uma API no PostgreSQL.""" + """DAG para buscar e armazenar contratos por órgão definido.""" @task def fetch_and_store_contratos() -> None: - logging.info("[contratos_ingest_dag.py] Starting fetch_and_store_contratos task") + logging.info("[contratos_ingest_dag.py] Iniciando extração") + + orgao_alvo = Variable.get("ORGAO_ALVO", default_var=None) + if not orgao_alvo: + logging.error("Variável ORGAO_ALVO não definida!") + raise ValueError("ORGAO_ALVO não definida") + + config_path = os.path.join( + os.environ.get("AIRFLOW_HOME", "/opt/airflow"), "configs/orgaos.yaml" + ) + with open(config_path, "r") as f: + config = yaml.safe_load(f) + + orgaos = config.get("orgaos", {}) + codigos_ug = orgaos.get(orgao_alvo, {}).get("codigos_ug", []) + + if not codigos_ug: + logging.warning(f"Nenhum código UG encontrado para o órgão '{orgao_alvo}'") + return + api = ClienteContratos() postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) - ug_codes = [113601, 113602] - for ug_code in ug_codes: - logging.info( - f"[contratos_ingest_dag.py] Fetching contratos for UG code: {ug_code}" - ) + for ug_code in codigos_ug: + logging.info(f"Buscando contratos para UG: {ug_code}") contratos = api.get_contratos_by_ug(ug_code) + if contratos: - logging.info( - f"[contratos_ingest_dag.py] Inserting contratos for UG code: " - f"{ug_code} into PostgreSQL" - ) + logging.info(f"Inserindo contratos da UG {ug_code} no schema compras_gov") db.insert_data( contratos, "contratos", @@ -47,9 +64,7 @@ def fetch_and_store_contratos() -> None: schema="compras_gov", ) else: - logging.warning( - f"[contratos_ingest_dag.py] No contratos found for UG code: {ug_code}" - ) + logging.warning(f"Nenhum contrato encontrado para UG {ug_code}") trigger_contratos_inativos = TriggerDagRunOperator( task_id="trigger_contratos_inativos", diff --git a/airflow_lappis/dags/data_ingest/nota_empenho_siafi_ingest_dag.py b/airflow_lappis/dags/data_ingest/nota_empenho_siafi_ingest_dag.py index 3a92135a..eb9b5b9b 100644 --- a/airflow_lappis/dags/data_ingest/nota_empenho_siafi_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/nota_empenho_siafi_ingest_dag.py @@ -1,4 +1,8 @@ +import logging +import os +import yaml from airflow.decorators import dag, task +from airflow.models import Variable from datetime import datetime, timedelta from cliente_siafi import ClienteSiafi from cliente_postgres import ClientPostgresDB @@ -19,10 +23,29 @@ def nota_empenho_siafi_ingest_dag() -> None: @task def fetch_and_store_notas_empenho() -> None: + logging.info("Iniciando fetch_and_store_notas_empenho") + + orgao_alvo = Variable.get("ORGAO_ALVO", default_var=None) + if not orgao_alvo: + logging.error("Variável ORGAO_ALVO não definida no Airflow!") + raise ValueError("ORGAO_ALVO não definida no Airflow") + + config_path = os.path.join( + os.environ.get("AIRFLOW_HOME", "/opt/airflow"), "configs/orgaos.yaml" + ) + with open(config_path, "r") as f: + config = yaml.safe_load(f) + + orgaos = config.get("orgaos", {}) + ugs_emitentes = orgaos.get(orgao_alvo, {}).get("codigos_ug", []) + + if not ugs_emitentes: + logging.warning(f"Nenhum código UG encontrado para o órgão '{orgao_alvo}'") + return + cliente = ClienteSiafi() postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) - ugs_emitentes = ["113601", "113602"] ano_atual = datetime.now().year for ug in ugs_emitentes: diff --git a/airflow_lappis/dags/data_ingest/notas_de_credito_ingest_dag.py b/airflow_lappis/dags/data_ingest/notas_de_credito_ingest_dag.py index cf829ff3..5755fc91 100644 --- a/airflow_lappis/dags/data_ingest/notas_de_credito_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/notas_de_credito_ingest_dag.py @@ -1,5 +1,8 @@ import logging +import os +import yaml from airflow.decorators import dag, task +from airflow.models import Variable from datetime import datetime, timedelta from postgres_helpers import get_postgres_conn from cliente_postgres import ClientPostgresDB @@ -21,11 +24,29 @@ def notas_de_credito_dag() -> None: @task def fetch_and_store_notas_de_credito() -> None: + logging.info("Iniciando fetch_and_store_notas_de_credito") + + orgao_alvo = Variable.get("ORGAO_ALVO", default_var=None) + if not orgao_alvo: + logging.error("Variável ORGAO_ALVO não definida no Airflow!") + raise ValueError("ORGAO_ALVO não definida no Airflow") + + config_path = os.path.join( + os.environ.get("AIRFLOW_HOME", "/opt/airflow"), "configs/orgaos.yaml" + ) + with open(config_path, "r") as f: + config = yaml.safe_load(f) + + orgaos = config.get("orgaos", {}) + ug_codes = orgaos.get(orgao_alvo, {}).get("codigos_ug", []) + + if not ug_codes: + logging.warning(f"Nenhum código UG encontrado para o órgão '{orgao_alvo}'") + return api = ClienteTed() postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) - ug_codes = [113601, 113602] for ug_code in ug_codes: notas_de_credito = api.get_notas_de_credito_by_ug(ug_code) diff --git a/airflow_lappis/dags/data_ingest/programacao_financeira_ingest_dag.py b/airflow_lappis/dags/data_ingest/programacao_financeira_ingest_dag.py index 1a2ecc9d..14a96db0 100644 --- a/airflow_lappis/dags/data_ingest/programacao_financeira_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/programacao_financeira_ingest_dag.py @@ -1,5 +1,8 @@ import logging +import os +import yaml from airflow.decorators import dag, task +from airflow.models import Variable from datetime import datetime, timedelta from postgres_helpers import get_postgres_conn from cliente_postgres import ClientPostgresDB @@ -21,11 +24,29 @@ def programacao_financeira_dag() -> None: @task def fetch_and_store_programacao_financeira() -> None: + logging.info("Iniciando fetch_and_store_programacao_financeira") + + orgao_alvo = Variable.get("ORGAO_ALVO", default_var=None) + if not orgao_alvo: + logging.error("Variável ORGAO_ALVO não definida no Airflow!") + raise ValueError("ORGAO_ALVO não definida no Airflow") + + config_path = os.path.join( + os.environ.get("AIRFLOW_HOME", "/opt/airflow"), "configs/orgaos.yaml" + ) + with open(config_path, "r") as f: + config = yaml.safe_load(f) + + orgaos = config.get("orgaos", {}) + ug_codes = orgaos.get(orgao_alvo, {}).get("codigos_ug", []) + + if not ug_codes: + logging.warning(f"Nenhum código UG encontrado para o órgão '{orgao_alvo}'") + return api = ClienteTed() postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) - ug_codes = [113601, 113602] for ug_code in ug_codes: programacao_financeira = api.get_programacao_financeira_by_ug(ug_code) diff --git a/docker-compose.yml b/docker-compose.yml index d8a7ec4c..a3a86c37 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,7 @@ x-airflow-common: &airflow-common - ./airflow_lappis/plugins:${AIRFLOW_HOME}/plugins/ - ./airflow_lappis/helpers:${AIRFLOW_HOME}/helpers/ - ./airflow_lappis/dags/dbt/ipea/profiles.yml:${AIRFLOW_HOME}/.dbt/profiles.yml + - ./airflow_lappis/configs:${AIRFLOW_HOME}/configs/ depends_on: &airflow-common-depends-on postgres: condition: service_healthy From 2bd09519159e9efbc64aca3be7e9d228fb87eb3d Mon Sep 17 00:00:00 2001 From: Matheus Dias Maciel Date: Mon, 31 Mar 2025 15:09:13 +0200 Subject: [PATCH 035/317] Ignoring non empty logs folder in Astronomer Cosmos --- airflow_lappis/dags/dbt/ipea/cosmos_dag.py | 27 ++++ requirements.txt | 168 ++++++++++++++++++--- setup-git-hooks.sh | 2 +- 3 files changed, 177 insertions(+), 20 deletions(-) diff --git a/airflow_lappis/dags/dbt/ipea/cosmos_dag.py b/airflow_lappis/dags/dbt/ipea/cosmos_dag.py index af77f3eb..5133b4bf 100755 --- a/airflow_lappis/dags/dbt/ipea/cosmos_dag.py +++ b/airflow_lappis/dags/dbt/ipea/cosmos_dag.py @@ -1,6 +1,32 @@ import os from datetime import datetime +from typing import Any + +from airflow.utils.context import Context from cosmos import DbtDag, ProjectConfig, ProfileConfig, ExecutionConfig +from cosmos.operators.local import DbtRunLocalOperator + + +class CustomDbtRunLocalOperator(DbtRunLocalOperator): + def build_and_run_cmd( + self, + context: Context, + cmd_flags: list[str] | None = None, + run_as_async: bool = False, + async_context: dict[str, Any] | None = None, + ) -> Any: + try: + return super().build_and_run_cmd( + context, cmd_flags, run_as_async, async_context + ) + except OSError as e: + if "Directory not empty: 'logs'" in str(e): + self.log.info( + "Ignoring non-empty logs directory error. Task was successful." + ) + return None + raise + profile_config = ProfileConfig( profiles_yml_filepath=f"{os.environ['AIRFLOW_REPO_BASE']}/dags/dbt/ipea/profiles.yml", @@ -19,4 +45,5 @@ catchup=False, dag_id="ipea_cosmos_dag", default_args={"retries": 2}, + operator_class=CustomDbtRunLocalOperator, ) diff --git a/requirements.txt b/requirements.txt index 45a41110..c76a1e4a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,38 +1,168 @@ aenum==3.1.15 ; python_version >= "3.11" and python_version < "3.12" -protobuf==5.29.3 ; python_version >= "3.11" and python_version < "3.12" agate==1.9.1 ; python_version >= "3.11" and python_version < "3.12" -aiohappyeyeballs==2.5.0 ; python_version >= "3.11" and python_version < "3.12" -apispec[yaml]==6.4.0 ; python_version >= "3.11" and python_version < "3.12" -astronomer-cosmos==1.9.0 ; python_version >= "3.11" and python_version < "3.12" -connexion[flask]==2.14.2 ; python_version >= "3.11" and python_version < "3.12" +aiohappyeyeballs==2.6.1 ; python_version >= "3.11" and python_version < "3.12" +aiohttp==3.10.11 ; python_version >= "3.11" and python_version < "3.12" +aiosignal==1.3.2 ; python_version >= "3.11" and python_version < "3.12" +alembic==1.15.2 ; python_version >= "3.11" and python_version < "3.12" +annotated-types==0.7.0 ; python_version >= "3.11" and python_version < "3.12" +anyio==4.9.0 ; python_version >= "3.11" and python_version < "3.12" +apache-airflow-providers-common-io==1.4.2 ; python_version >= "3.11" and python_version < "3.12" +apache-airflow-providers-common-sql==1.20.0 ; python_version >= "3.11" and python_version < "3.12" +apache-airflow-providers-ftp==3.11.1 ; python_version >= "3.11" and python_version < "3.12" +apache-airflow-providers-http==4.13.3 ; python_version >= "3.11" and python_version < "3.12" +apache-airflow-providers-imap==3.7.0 ; python_version >= "3.11" and python_version < "3.12" +apache-airflow-providers-postgres==5.14.0 ; python_version >= "3.11" and python_version < "3.12" +apache-airflow-providers-sqlite==3.9.1 ; python_version >= "3.11" and python_version < "3.12" +apache-airflow==2.8.1 ; python_version >= "3.11" and python_version < "3.12" +apispec[yaml]==6.8.1 ; python_version >= "3.11" and python_version < "3.12" +argcomplete==3.6.1 ; python_version >= "3.11" and python_version < "3.12" +asgiref==3.8.1 ; python_version >= "3.11" and python_version < "3.12" +astronomer-cosmos==1.9.2 ; python_version >= "3.11" and python_version < "3.12" +attrs==25.3.0 ; python_version >= "3.11" and python_version < "3.12" +babel==2.17.0 ; python_version >= "3.11" and python_version < "3.12" +blinker==1.9.0 ; python_version >= "3.11" and python_version < "3.12" +cachelib==0.13.0 ; python_version >= "3.11" and python_version < "3.12" +certifi==2025.1.31 ; python_version >= "3.11" and python_version < "3.12" +cffi==1.17.1 ; python_version >= "3.11" and python_version < "3.12" and platform_python_implementation != "PyPy" +charset-normalizer==3.4.1 ; python_version >= "3.11" and python_version < "3.12" +click==8.1.8 ; python_version >= "3.11" and python_version < "3.12" +clickclick==20.10.2 ; python_version >= "3.11" and python_version < "3.12" +colorama==0.4.6 ; python_version >= "3.11" and python_version < "3.12" +colorlog==4.8.0 ; python_version >= "3.11" and python_version < "3.12" +configupdater==3.2 ; python_version >= "3.11" and python_version < "3.12" +connexion[flask]==2.14.1 ; python_version >= "3.11" and python_version < "3.12" +cron-descriptor==1.4.5 ; python_version >= "3.11" and python_version < "3.12" +croniter==6.0.0 ; python_version >= "3.11" and python_version < "3.12" +cryptography==44.0.2 ; python_version >= "3.11" and python_version < "3.12" daff==1.3.46 ; python_version >= "3.11" and python_version < "3.12" -dbt-adapters==1.14.1 ; python_version >= "3.11" and python_version < "3.12" -dbt-common==1.15.0 ; python_version >= "3.11" and python_version < "3.12" +dbt-adapters==1.14.3 ; python_version >= "3.11" and python_version < "3.12" +dbt-common==1.16.0 ; python_version >= "3.11" and python_version < "3.12" dbt-core==1.9.3 ; python_version >= "3.11" and python_version < "3.12" dbt-extractor==0.5.1 ; python_version >= "3.11" and python_version < "3.12" dbt-postgres==1.9.0 ; python_version >= "3.11" and python_version < "3.12" dbt-semantic-interfaces==0.7.4 ; python_version >= "3.11" and python_version < "3.12" -opentelemetry-api==1.30.0 ; python_version >= "3.11" and python_version < "3.12" -opentelemetry-exporter-otlp-proto-common==1.30.0 ; python_version >= "3.11" and python_version < "3.12" -opentelemetry-exporter-otlp-proto-grpc==1.30.0 ; python_version >= "3.11" and python_version < "3.12" -opentelemetry-exporter-otlp-proto-http==1.30.0 ; python_version >= "3.11" and python_version < "3.12" -opentelemetry-exporter-otlp==1.30.0 ; python_version >= "3.11" and python_version < "3.12" -opentelemetry-proto==1.30.0 ; python_version >= "3.11" and python_version < "3.12" -opentelemetry-sdk==1.30.0 ; python_version >= "3.11" and python_version < "3.12" -opentelemetry-semantic-conventions==0.51b0 ; python_version >= "3.11" and python_version < "3.12" deepdiff==7.0.1 ; python_version >= "3.11" and python_version < "3.12" +deprecated==1.2.18 ; python_version >= "3.11" and python_version < "3.12" deprecation==2.1.0 ; python_version >= "3.11" and python_version < "3.12" +dill==0.3.9 ; python_version >= "3.11" and python_version < "3.12" +distlib==0.3.9 ; python_version >= "3.11" and python_version < "3.12" +dnspython==2.7.0 ; python_version >= "3.11" and python_version < "3.12" +email-validator==1.3.1 ; python_version >= "3.11" and python_version < "3.12" +filelock==3.18.0 ; python_version >= "3.11" and python_version < "3.12" +flask-appbuilder==4.3.10 ; python_version >= "3.11" and python_version < "3.12" +flask-babel==2.0.0 ; python_version >= "3.11" and python_version < "3.12" +flask-caching==2.3.1 ; python_version >= "3.11" and python_version < "3.12" +flask-jwt-extended==4.7.1 ; python_version >= "3.11" and python_version < "3.12" +flask-limiter==3.12 ; python_version >= "3.11" and python_version < "3.12" +flask-login==0.6.3 ; python_version >= "3.11" and python_version < "3.12" +flask-session==0.5.0 ; python_version >= "3.11" and python_version < "3.12" +flask-sqlalchemy==2.5.1 ; python_version >= "3.11" and python_version < "3.12" +flask-wtf==1.2.2 ; python_version >= "3.11" and python_version < "3.12" +flask==2.2.5 ; python_version >= "3.11" and python_version < "3.12" +frozenlist==1.5.0 ; python_version >= "3.11" and python_version < "3.12" +fsspec==2025.3.0 ; python_version >= "3.11" and python_version < "3.12" +google-re2==1.1.20240702 ; python_version >= "3.11" and python_version < "3.12" +googleapis-common-protos==1.69.2 ; python_version >= "3.11" and python_version < "3.12" +greenlet==3.1.1 ; python_version >= "3.11" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32") and python_version < "3.12" +grpcio==1.71.0 ; python_version >= "3.11" and python_version < "3.12" +gunicorn==23.0.0 ; python_version >= "3.11" and python_version < "3.12" +h11==0.14.0 ; python_version >= "3.11" and python_version < "3.12" +httpcore==1.0.7 ; python_version >= "3.11" and python_version < "3.12" +httpx==0.28.1 ; python_version >= "3.11" and python_version < "3.12" +idna==3.10 ; python_version >= "3.11" and python_version < "3.12" imap-tools==1.10.0 ; python_version >= "3.11" and python_version < "3.12" +importlib-metadata==6.11.0 ; python_version >= "3.11" and python_version < "3.12" +inflection==0.5.1 ; python_version >= "3.11" and python_version < "3.12" +isodate==0.6.1 ; python_version >= "3.11" and python_version < "3.12" +itsdangerous==2.2.0 ; python_version >= "3.11" and python_version < "3.12" +jinja2==3.1.6 ; python_version >= "3.11" and python_version < "3.12" +jsonschema-specifications==2024.10.1 ; python_version >= "3.11" and python_version < "3.12" +jsonschema==4.23.0 ; python_version >= "3.11" and python_version < "3.12" +lazy-object-proxy==1.10.0 ; python_version >= "3.11" and python_version < "3.12" leather==0.4.0 ; python_version >= "3.11" and python_version < "3.12" +limits==4.4.1 ; python_version >= "3.11" and python_version < "3.12" +linkify-it-py==2.0.3 ; python_version >= "3.11" and python_version < "3.12" +lockfile==0.12.2 ; python_version >= "3.11" and python_version < "3.12" +lxml==5.3.1 ; python_version >= "3.11" and python_version < "3.12" +mako==1.3.9 ; python_version >= "3.11" and python_version < "3.12" +markdown-it-py==3.0.0 ; python_version >= "3.11" and python_version < "3.12" +markdown==3.7 ; python_version >= "3.11" and python_version < "3.12" +markupsafe==3.0.2 ; python_version >= "3.11" and python_version < "3.12" +marshmallow-oneofschema==3.1.1 ; python_version >= "3.11" and python_version < "3.12" +marshmallow-sqlalchemy==0.26.1 ; python_version >= "3.11" and python_version < "3.12" +marshmallow==3.26.1 ; python_version >= "3.11" and python_version < "3.12" mashumaro[msgpack]==3.14 ; python_version >= "3.11" and python_version < "3.12" +mdit-py-plugins==0.4.2 ; python_version >= "3.11" and python_version < "3.12" +mdurl==0.1.2 ; python_version >= "3.11" and python_version < "3.12" more-itertools==10.6.0 ; python_version >= "3.11" and python_version < "3.12" msgpack==1.1.0 ; python_version >= "3.11" and python_version < "3.12" +multidict==6.2.0 ; python_version >= "3.11" and python_version < "3.12" networkx==3.4.2 ; python_version >= "3.11" and python_version < "3.12" +numpy==1.26.4 ; python_version >= "3.11" and python_version < "3.12" +opentelemetry-api==1.31.1 ; python_version >= "3.11" and python_version < "3.12" +opentelemetry-exporter-otlp-proto-common==1.31.1 ; python_version >= "3.11" and python_version < "3.12" +opentelemetry-exporter-otlp-proto-grpc==1.31.1 ; python_version >= "3.11" and python_version < "3.12" +opentelemetry-exporter-otlp-proto-http==1.31.1 ; python_version >= "3.11" and python_version < "3.12" +opentelemetry-exporter-otlp==1.31.1 ; python_version >= "3.11" and python_version < "3.12" +opentelemetry-proto==1.31.1 ; python_version >= "3.11" and python_version < "3.12" +opentelemetry-sdk==1.31.1 ; python_version >= "3.11" and python_version < "3.12" +opentelemetry-semantic-conventions==0.52b1 ; python_version >= "3.11" and python_version < "3.12" +ordered-set==4.1.0 ; python_version >= "3.11" and python_version < "3.12" +packaging==24.2 ; python_version >= "3.11" and python_version < "3.12" +pandas==2.2.3 ; python_version >= "3.11" and python_version < "3.12" parsedatetime==2.6 ; python_version >= "3.11" and python_version < "3.12" -propcache==0.3.0 ; python_version >= "3.11" and python_version < "3.12" -pydantic-core==2.27.2 ; python_version >= "3.11" and python_version < "3.12" +pathspec==0.12.1 ; python_version >= "3.11" and python_version < "3.12" +pendulum==3.0.0 ; python_version >= "3.11" and python_version < "3.12" +platformdirs==4.3.7 ; python_version >= "3.11" and python_version < "3.12" +pluggy==1.5.0 ; python_version >= "3.11" and python_version < "3.12" +prison==0.2.1 ; python_version >= "3.11" and python_version < "3.12" +propcache==0.3.1 ; python_version >= "3.11" and python_version < "3.12" +protobuf==5.29.4 ; python_version >= "3.11" and python_version < "3.12" +psutil==7.0.0 ; python_version >= "3.11" and python_version < "3.12" +psycopg2-binary==2.9.10 ; python_version >= "3.11" and python_version < "3.12" +pycparser==2.22 ; python_version >= "3.11" and python_version < "3.12" and platform_python_implementation != "PyPy" +pydantic-core==2.33.0 ; python_version >= "3.11" and python_version < "3.12" +pydantic==2.11.1 ; python_version >= "3.11" and python_version < "3.12" +pygments==2.19.1 ; python_version >= "3.11" and python_version < "3.12" +pyjwt==2.10.1 ; python_version >= "3.11" and python_version < "3.12" +python-daemon==3.1.2 ; python_version >= "3.11" and python_version < "3.12" +python-dateutil==2.9.0.post0 ; python_version >= "3.11" and python_version < "3.12" +python-nvd3==0.16.0 ; python_version >= "3.11" and python_version < "3.12" +python-slugify==8.0.4 ; python_version >= "3.11" and python_version < "3.12" pytimeparse==1.1.8 ; python_version >= "3.11" and python_version < "3.12" +pytz==2025.2 ; python_version >= "3.11" and python_version < "3.12" +pyyaml==6.0.2 ; python_version >= "3.11" and python_version < "3.12" +referencing==0.36.2 ; python_version >= "3.11" and python_version < "3.12" requests-file==2.1.0 ; python_version >= "3.11" and python_version < "3.12" +requests-toolbelt==1.0.0 ; python_version >= "3.11" and python_version < "3.12" +requests==2.32.3 ; python_version >= "3.11" and python_version < "3.12" +rfc3339-validator==0.1.4 ; python_version >= "3.11" and python_version < "3.12" +rich-argparse==1.7.0 ; python_version >= "3.11" and python_version < "3.12" +rich==13.9.4 ; python_version >= "3.11" and python_version < "3.12" +rpds-py==0.24.0 ; python_version >= "3.11" and python_version < "3.12" +setproctitle==1.3.5 ; python_version >= "3.11" and python_version < "3.12" +six==1.17.0 ; python_version >= "3.11" and python_version < "3.12" +sniffio==1.3.1 ; python_version >= "3.11" and python_version < "3.12" snowplow-tracker==1.1.0 ; python_version >= "3.11" and python_version < "3.12" -typing-extensions==4.12.2 ; python_version >= "3.11" and python_version < "3.12" -zeep==4.3.1 ; python_version >= "3.11" and python_version < "3.12" \ No newline at end of file +sqlalchemy-jsonfield==1.0.2 ; python_version >= "3.11" and python_version < "3.12" +sqlalchemy-utils==0.41.2 ; python_version >= "3.11" and python_version < "3.12" +sqlalchemy==1.4.54 ; python_version >= "3.11" and python_version < "3.12" +sqlparse==0.5.3 ; python_version >= "3.11" and python_version < "3.12" +tabulate==0.9.0 ; python_version >= "3.11" and python_version < "3.12" +tenacity==9.0.0 ; python_version >= "3.11" and python_version < "3.12" +termcolor==2.5.0 ; python_version >= "3.11" and python_version < "3.12" +text-unidecode==1.3 ; python_version >= "3.11" and python_version < "3.12" +typing-extensions==4.13.0 ; python_version >= "3.11" and python_version < "3.12" +typing-inspection==0.4.0 ; python_version >= "3.11" and python_version < "3.12" +tzdata==2025.2 ; python_version >= "3.11" and python_version < "3.12" +uc-micro-py==1.0.3 ; python_version >= "3.11" and python_version < "3.12" +unicodecsv==0.14.1 ; python_version >= "3.11" and python_version < "3.12" +universal-pathlib==0.2.6 ; python_version >= "3.11" and python_version < "3.12" +urllib3==2.3.0 ; python_version >= "3.11" and python_version < "3.12" +virtualenv==20.29.3 ; python_version >= "3.11" and python_version < "3.12" +werkzeug==2.3.8 ; python_version >= "3.11" and python_version < "3.12" +wrapt==1.17.2 ; python_version >= "3.11" and python_version < "3.12" +wtforms==3.2.1 ; python_version >= "3.11" and python_version < "3.12" +yarl==1.18.3 ; python_version >= "3.11" and python_version < "3.12" +zeep==4.3.1 ; python_version >= "3.11" and python_version < "3.12" +zipp==3.21.0 ; python_version >= "3.11" and python_version < "3.12" diff --git a/setup-git-hooks.sh b/setup-git-hooks.sh index cb9dbcf7..21c1234c 100644 --- a/setup-git-hooks.sh +++ b/setup-git-hooks.sh @@ -17,7 +17,7 @@ cat > .git/hooks/pre-push << 'EOF' #!/bin/bash set -e echo "Running pre-push checks..." -make lint +make lint -e GITLAB_CI=TRUE make test echo -e "\033[0;32mPre-push checks passed!\033[0m" exit 0 From e3899e3fa9cc3852412c857a754c67ae2f7781e1 Mon Sep 17 00:00:00 2001 From: Pedro Rossi Date: Mon, 31 Mar 2025 15:18:22 +0000 Subject: [PATCH 036/317] feat: adicionando CD --- .gitlab-ci.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 46cb39e4..58a8a7b1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -11,16 +11,20 @@ variables: DBT_PROJECT_DIR: "${CI_PROJECT_DIR}/plugins/dbt" GIT_CI_USER: "ci bot" GIT_CI_EMAIL: "ci.lappis.rocks@gmail.com" + DOCKER_TLS_CERTDIR: "/certs" + IMAGE_TAG: "$CI_REGISTRY_IMAGE/airflow-ipea:$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA" cache: paths: - .cache/poetry - .cache/pip - .venv + - .cache/docker stages: - lint - test + - build .install-poetry: before_script: @@ -65,3 +69,20 @@ scan: - plugins/** - src/** - tests/** + +docker-build-and-push: + stage: build + image: docker:20.10.16 + services: + - docker:20.10.16-dind + script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - docker build -t $IMAGE_TAG . + - docker push $IMAGE_TAG + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + - changes: + - Dockerfile + - requirements.txt + - pyproject.toml From 454a460635b1b1d18029c56b715ee359de2b7e11 Mon Sep 17 00:00:00 2001 From: Matheus Dias Maciel Date: Tue, 1 Apr 2025 20:12:13 +0200 Subject: [PATCH 037/317] Redirecting dbt logs to another folder --- airflow_lappis/dags/dbt/ipea/cosmos_dag.py | 30 +++------------------- requirements.txt | 10 ++++---- 2 files changed, 9 insertions(+), 31 deletions(-) diff --git a/airflow_lappis/dags/dbt/ipea/cosmos_dag.py b/airflow_lappis/dags/dbt/ipea/cosmos_dag.py index 5133b4bf..4b586210 100755 --- a/airflow_lappis/dags/dbt/ipea/cosmos_dag.py +++ b/airflow_lappis/dags/dbt/ipea/cosmos_dag.py @@ -1,32 +1,11 @@ import os from datetime import datetime -from typing import Any - -from airflow.utils.context import Context from cosmos import DbtDag, ProjectConfig, ProfileConfig, ExecutionConfig -from cosmos.operators.local import DbtRunLocalOperator - - -class CustomDbtRunLocalOperator(DbtRunLocalOperator): - def build_and_run_cmd( - self, - context: Context, - cmd_flags: list[str] | None = None, - run_as_async: bool = False, - async_context: dict[str, Any] | None = None, - ) -> Any: - try: - return super().build_and_run_cmd( - context, cmd_flags, run_as_async, async_context - ) - except OSError as e: - if "Directory not empty: 'logs'" in str(e): - self.log.info( - "Ignoring non-empty logs directory error. Task was successful." - ) - return None - raise +from cosmos.constants import DBT_LOG_PATH_ENVVAR +dbt_log_path = "/tmp/dbt_logs" +os.makedirs(dbt_log_path, exist_ok=True) +os.environ[DBT_LOG_PATH_ENVVAR] = dbt_log_path profile_config = ProfileConfig( profiles_yml_filepath=f"{os.environ['AIRFLOW_REPO_BASE']}/dags/dbt/ipea/profiles.yml", @@ -45,5 +24,4 @@ def build_and_run_cmd( catchup=False, dag_id="ipea_cosmos_dag", default_args={"retries": 2}, - operator_class=CustomDbtRunLocalOperator, ) diff --git a/requirements.txt b/requirements.txt index c76a1e4a..3b4cef47 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,8 +35,8 @@ cron-descriptor==1.4.5 ; python_version >= "3.11" and python_version < "3.12" croniter==6.0.0 ; python_version >= "3.11" and python_version < "3.12" cryptography==44.0.2 ; python_version >= "3.11" and python_version < "3.12" daff==1.3.46 ; python_version >= "3.11" and python_version < "3.12" -dbt-adapters==1.14.3 ; python_version >= "3.11" and python_version < "3.12" -dbt-common==1.16.0 ; python_version >= "3.11" and python_version < "3.12" +dbt-adapters==1.14.4 ; python_version >= "3.11" and python_version < "3.12" +dbt-common==1.17.0 ; python_version >= "3.11" and python_version < "3.12" dbt-core==1.9.3 ; python_version >= "3.11" and python_version < "3.12" dbt-extractor==0.5.1 ; python_version >= "3.11" and python_version < "3.12" dbt-postgres==1.9.0 ; python_version >= "3.11" and python_version < "3.12" @@ -60,7 +60,7 @@ flask-sqlalchemy==2.5.1 ; python_version >= "3.11" and python_version < "3.12" flask-wtf==1.2.2 ; python_version >= "3.11" and python_version < "3.12" flask==2.2.5 ; python_version >= "3.11" and python_version < "3.12" frozenlist==1.5.0 ; python_version >= "3.11" and python_version < "3.12" -fsspec==2025.3.0 ; python_version >= "3.11" and python_version < "3.12" +fsspec==2025.3.2 ; python_version >= "3.11" and python_version < "3.12" google-re2==1.1.20240702 ; python_version >= "3.11" and python_version < "3.12" googleapis-common-protos==1.69.2 ; python_version >= "3.11" and python_version < "3.12" greenlet==3.1.1 ; python_version >= "3.11" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32") and python_version < "3.12" @@ -150,7 +150,7 @@ sqlalchemy==1.4.54 ; python_version >= "3.11" and python_version < "3.12" sqlparse==0.5.3 ; python_version >= "3.11" and python_version < "3.12" tabulate==0.9.0 ; python_version >= "3.11" and python_version < "3.12" tenacity==9.0.0 ; python_version >= "3.11" and python_version < "3.12" -termcolor==2.5.0 ; python_version >= "3.11" and python_version < "3.12" +termcolor==3.0.0 ; python_version >= "3.11" and python_version < "3.12" text-unidecode==1.3 ; python_version >= "3.11" and python_version < "3.12" typing-extensions==4.13.0 ; python_version >= "3.11" and python_version < "3.12" typing-inspection==0.4.0 ; python_version >= "3.11" and python_version < "3.12" @@ -159,7 +159,7 @@ uc-micro-py==1.0.3 ; python_version >= "3.11" and python_version < "3.12" unicodecsv==0.14.1 ; python_version >= "3.11" and python_version < "3.12" universal-pathlib==0.2.6 ; python_version >= "3.11" and python_version < "3.12" urllib3==2.3.0 ; python_version >= "3.11" and python_version < "3.12" -virtualenv==20.29.3 ; python_version >= "3.11" and python_version < "3.12" +virtualenv==20.30.0 ; python_version >= "3.11" and python_version < "3.12" werkzeug==2.3.8 ; python_version >= "3.11" and python_version < "3.12" wrapt==1.17.2 ; python_version >= "3.11" and python_version < "3.12" wtforms==3.2.1 ; python_version >= "3.11" and python_version < "3.12" From a1cf6d32404684259f632a53fd11e988955506ed Mon Sep 17 00:00:00 2001 From: Joyce Dionizio Date: Wed, 2 Apr 2025 13:57:45 +0000 Subject: [PATCH 038/317] refactor(dags): altera logica para setar variaveis --- airflow_lappis/configs/orgaos.yaml | 16 ---------------- .../dags/data_ingest/contratos_ingest_dag.py | 17 ++++++----------- docker-compose.yml | 1 - 3 files changed, 6 insertions(+), 28 deletions(-) delete mode 100644 airflow_lappis/configs/orgaos.yaml diff --git a/airflow_lappis/configs/orgaos.yaml b/airflow_lappis/configs/orgaos.yaml deleted file mode 100644 index da253161..00000000 --- a/airflow_lappis/configs/orgaos.yaml +++ /dev/null @@ -1,16 +0,0 @@ -orgaos: - ipea: - codigos_ug: - - 113601 - - 113602 - unb: - codigos_ug: - - 154040 - ibama: - codigos_ug: - - 440001 - - 440048 - - 440050 - mgi: - codigos_ug: - - 201082 \ No newline at end of file diff --git a/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py b/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py index 7cb1ce95..2b00db1a 100755 --- a/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py @@ -1,5 +1,4 @@ import logging -import os import yaml from airflow.decorators import dag, task from airflow.operators.trigger_dagrun import TriggerDagRunOperator @@ -28,19 +27,15 @@ def api_contratos_dag() -> None: def fetch_and_store_contratos() -> None: logging.info("[contratos_ingest_dag.py] Iniciando extração") - orgao_alvo = Variable.get("ORGAO_ALVO", default_var=None) + orgao_alvo = Variable.get("airflow_orgao", default_var=None) if not orgao_alvo: - logging.error("Variável ORGAO_ALVO não definida!") - raise ValueError("ORGAO_ALVO não definida") + logging.error("Variável airflow_orgao não definida!") + raise ValueError("airflow_orgao não definida") - config_path = os.path.join( - os.environ.get("AIRFLOW_HOME", "/opt/airflow"), "configs/orgaos.yaml" - ) - with open(config_path, "r") as f: - config = yaml.safe_load(f) + orgaos_config_str = Variable.get("airflow_variables", default_var="{}") + orgaos_config = yaml.safe_load(orgaos_config_str) - orgaos = config.get("orgaos", {}) - codigos_ug = orgaos.get(orgao_alvo, {}).get("codigos_ug", []) + codigos_ug = orgaos_config.get(orgao_alvo, {}).get("codigos_ug", []) if not codigos_ug: logging.warning(f"Nenhum código UG encontrado para o órgão '{orgao_alvo}'") diff --git a/docker-compose.yml b/docker-compose.yml index a3a86c37..d8a7ec4c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,7 +10,6 @@ x-airflow-common: &airflow-common - ./airflow_lappis/plugins:${AIRFLOW_HOME}/plugins/ - ./airflow_lappis/helpers:${AIRFLOW_HOME}/helpers/ - ./airflow_lappis/dags/dbt/ipea/profiles.yml:${AIRFLOW_HOME}/.dbt/profiles.yml - - ./airflow_lappis/configs:${AIRFLOW_HOME}/configs/ depends_on: &airflow-common-depends-on postgres: condition: service_healthy From d96dee9579d39896f0bff217e439d7e62c1d4c55 Mon Sep 17 00:00:00 2001 From: Joyce Date: Wed, 2 Apr 2025 19:58:08 -0300 Subject: [PATCH 039/317] feat(dags): corrige variaveis no restante das dags --- .../nota_empenho_siafi_ingest_dag.py | 17 +++++--------- .../notas_de_credito_ingest_dag.py | 20 ++++++----------- .../programacao_financeira_ingest_dag.py | 22 ++++++++----------- 3 files changed, 22 insertions(+), 37 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/nota_empenho_siafi_ingest_dag.py b/airflow_lappis/dags/data_ingest/nota_empenho_siafi_ingest_dag.py index eb9b5b9b..bdf77996 100644 --- a/airflow_lappis/dags/data_ingest/nota_empenho_siafi_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/nota_empenho_siafi_ingest_dag.py @@ -1,5 +1,4 @@ import logging -import os import yaml from airflow.decorators import dag, task from airflow.models import Variable @@ -25,19 +24,15 @@ def nota_empenho_siafi_ingest_dag() -> None: def fetch_and_store_notas_empenho() -> None: logging.info("Iniciando fetch_and_store_notas_empenho") - orgao_alvo = Variable.get("ORGAO_ALVO", default_var=None) + orgao_alvo = Variable.get("airflow_orgao", default_var=None) if not orgao_alvo: - logging.error("Variável ORGAO_ALVO não definida no Airflow!") - raise ValueError("ORGAO_ALVO não definida no Airflow") + logging.error("Variável airflow_orgao não definida!") + raise ValueError("airflow_orgao não definida") - config_path = os.path.join( - os.environ.get("AIRFLOW_HOME", "/opt/airflow"), "configs/orgaos.yaml" - ) - with open(config_path, "r") as f: - config = yaml.safe_load(f) + orgaos_config_str = Variable.get("airflow_variables", default_var="{}") + orgaos_config = yaml.safe_load(orgaos_config_str) - orgaos = config.get("orgaos", {}) - ugs_emitentes = orgaos.get(orgao_alvo, {}).get("codigos_ug", []) + ugs_emitentes = orgaos_config.get(orgao_alvo, {}).get("codigos_ug", []) if not ugs_emitentes: logging.warning(f"Nenhum código UG encontrado para o órgão '{orgao_alvo}'") diff --git a/airflow_lappis/dags/data_ingest/notas_de_credito_ingest_dag.py b/airflow_lappis/dags/data_ingest/notas_de_credito_ingest_dag.py index 5755fc91..7baabab5 100644 --- a/airflow_lappis/dags/data_ingest/notas_de_credito_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/notas_de_credito_ingest_dag.py @@ -1,5 +1,4 @@ import logging -import os import yaml from airflow.decorators import dag, task from airflow.models import Variable @@ -21,24 +20,19 @@ tags=["notas de credito", "ted_api"], ) def notas_de_credito_dag() -> None: - @task def fetch_and_store_notas_de_credito() -> None: logging.info("Iniciando fetch_and_store_notas_de_credito") - orgao_alvo = Variable.get("ORGAO_ALVO", default_var=None) + orgao_alvo = Variable.get("airflow_orgao", default_var=None) if not orgao_alvo: - logging.error("Variável ORGAO_ALVO não definida no Airflow!") - raise ValueError("ORGAO_ALVO não definida no Airflow") + logging.error("Variável airflow_orgao não definida!") + raise ValueError("airflow_orgao não definida") - config_path = os.path.join( - os.environ.get("AIRFLOW_HOME", "/opt/airflow"), "configs/orgaos.yaml" - ) - with open(config_path, "r") as f: - config = yaml.safe_load(f) + orgaos_config_str = Variable.get("airflow_variables", default_var="{}") + orgaos_config = yaml.safe_load(orgaos_config_str) - orgaos = config.get("orgaos", {}) - ug_codes = orgaos.get(orgao_alvo, {}).get("codigos_ug", []) + ug_codes = orgaos_config.get(orgao_alvo, {}).get("codigos_ug", []) if not ug_codes: logging.warning(f"Nenhum código UG encontrado para o órgão '{orgao_alvo}'") @@ -59,7 +53,7 @@ def fetch_and_store_notas_de_credito() -> None: schema="transfere_gov", ) else: - logging.warning(f"No notas de credito found for UG code: {ug_code}") + logging.warning(f"Nenhuma nota de crédito encontrada para UG {ug_code}") fetch_and_store_notas_de_credito() diff --git a/airflow_lappis/dags/data_ingest/programacao_financeira_ingest_dag.py b/airflow_lappis/dags/data_ingest/programacao_financeira_ingest_dag.py index 14a96db0..52b40dcb 100644 --- a/airflow_lappis/dags/data_ingest/programacao_financeira_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/programacao_financeira_ingest_dag.py @@ -1,5 +1,4 @@ import logging -import os import yaml from airflow.decorators import dag, task from airflow.models import Variable @@ -21,24 +20,19 @@ tags=["programacao_financeira", "ted_api"], ) def programacao_financeira_dag() -> None: - @task def fetch_and_store_programacao_financeira() -> None: logging.info("Iniciando fetch_and_store_programacao_financeira") - orgao_alvo = Variable.get("ORGAO_ALVO", default_var=None) + orgao_alvo = Variable.get("airflow_orgao", default_var=None) if not orgao_alvo: - logging.error("Variável ORGAO_ALVO não definida no Airflow!") - raise ValueError("ORGAO_ALVO não definida no Airflow") + logging.error("Variável airflow_orgao não definida!") + raise ValueError("airflow_orgao não definida") - config_path = os.path.join( - os.environ.get("AIRFLOW_HOME", "/opt/airflow"), "configs/orgaos.yaml" - ) - with open(config_path, "r") as f: - config = yaml.safe_load(f) + orgaos_config_str = Variable.get("airflow_variables", default_var="{}") + orgaos_config = yaml.safe_load(orgaos_config_str) - orgaos = config.get("orgaos", {}) - ug_codes = orgaos.get(orgao_alvo, {}).get("codigos_ug", []) + ug_codes = orgaos_config.get(orgao_alvo, {}).get("codigos_ug", []) if not ug_codes: logging.warning(f"Nenhum código UG encontrado para o órgão '{orgao_alvo}'") @@ -59,7 +53,9 @@ def fetch_and_store_programacao_financeira() -> None: schema="transfere_gov", ) else: - logging.warning(f"No programacao financeira found for UG code: {ug_code}") + logging.warning( + f"Nenhuma programação financeira encontrada para UG {ug_code}" + ) fetch_and_store_programacao_financeira() From c64d790e49348b2609d328c506d3367b6706fb9b Mon Sep 17 00:00:00 2001 From: Joyce Date: Wed, 2 Apr 2025 20:16:49 -0300 Subject: [PATCH 040/317] fix(dag): corrige variavel restante --- .../contratos_inativos_ingest_dag.py | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py b/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py index 03a23834..c940f31b 100755 --- a/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py @@ -1,5 +1,4 @@ import logging -import os import yaml from airflow.decorators import dag, task from airflow.models import Variable @@ -25,21 +24,17 @@ def api_contratos_inativos_dag() -> None: @task def fetch_and_store_contratos_inativos() -> None: - logging.info("Starting fetch_and_store_contratos_inativos task") + logging.info("Iniciando fetch_and_store_contratos_inativos") - orgao_alvo = Variable.get("ORGAO_ALVO", default_var=None) + orgao_alvo = Variable.get("airflow_orgao", default_var=None) if not orgao_alvo: - logging.error("Variável ORGAO_ALVO não definida no Airflow!") - raise ValueError("ORGAO_ALVO não definida no Airflow") + logging.error("Variável airflow_orgao não definida!") + raise ValueError("airflow_orgao não definida") - config_path = os.path.join( - os.environ.get("AIRFLOW_HOME", "/opt/airflow"), "configs/orgaos.yaml" - ) - with open(config_path, "r") as f: - config = yaml.safe_load(f) + orgaos_config_str = Variable.get("airflow_variables", default_var="{}") + orgaos_config = yaml.safe_load(orgaos_config_str) - orgaos = config.get("orgaos", {}) - ug_codes = orgaos.get(orgao_alvo, {}).get("codigos_ug", []) + ug_codes = orgaos_config.get(orgao_alvo, {}).get("codigos_ug", []) if not ug_codes: logging.warning(f"Nenhum código UG encontrado para o órgão '{orgao_alvo}'") @@ -50,11 +45,11 @@ def fetch_and_store_contratos_inativos() -> None: db = ClientPostgresDB(postgres_conn_str) for ug_code in ug_codes: - logging.info(f"Fetching contratos inativos for UG code: {ug_code}") + logging.info(f"Buscando contratos inativos para UG: {ug_code}") contratos = api.get_contratos_inativos_by_ug(ug_code) if contratos: logging.info( - f"Inserting contratos inativos for UG code: {ug_code} into PostgreSQL" + f"Inserindo contratos inativos da UG {ug_code} no schema compras_gov" ) db.insert_data( contratos, @@ -64,7 +59,7 @@ def fetch_and_store_contratos_inativos() -> None: schema="compras_gov", ) else: - logging.warning(f"No contratos inativos found for UG code: {ug_code}") + logging.warning(f"Nenhum contrato inativo encontrado para UG {ug_code}") fetch_and_store_contratos_inativos() From feb8a5571c58fbed975f2a8450587154f072fa9a Mon Sep 17 00:00:00 2001 From: VictorSzk Date: Sat, 5 Apr 2025 20:13:36 +0000 Subject: [PATCH 041/317] Modelos ted --- airflow_lappis/dags/dbt/ipea/dbt_project.yml | 10 +++- .../dags/dbt/ipea/macros/create_udfs.sql | 4 +- .../dbt/ipea/macros/parse_financial_value.sql | 19 ++++++ .../dags/dbt/ipea/macros/udfs/f_format_nc.sql | 19 ++++++ .../macros/udfs/f_parse_financial_value.sql | 16 ----- .../contratos_dbt/bronze/empenhos_tesouro.sql | 60 +++++++++++++------ .../models/contratos_dbt/bronze/estagios.sql | 12 ++++ .../contratos_dbt/silver/estagios_mensal.sql | 2 +- .../dags/dbt/ipea/models/sources.yml | 9 +++ .../ted_dbt/silver/empenhos_w_plano_acao.sql | 31 ++++++++++ .../silver/notas_credito_w_plano_acao.sql | 37 ++++++++++++ .../ted_dbt/views/num_transf_n_plano_acao.sql | 26 ++++++++ requirements.txt | 6 +- 13 files changed, 208 insertions(+), 43 deletions(-) create mode 100644 airflow_lappis/dags/dbt/ipea/macros/parse_financial_value.sql create mode 100644 airflow_lappis/dags/dbt/ipea/macros/udfs/f_format_nc.sql delete mode 100644 airflow_lappis/dags/dbt/ipea/macros/udfs/f_parse_financial_value.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/empenhos_w_plano_acao.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/notas_credito_w_plano_acao.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/ted_dbt/views/num_transf_n_plano_acao.sql diff --git a/airflow_lappis/dags/dbt/ipea/dbt_project.yml b/airflow_lappis/dags/dbt/ipea/dbt_project.yml index 3ac28327..7fea0776 100755 --- a/airflow_lappis/dags/dbt/ipea/dbt_project.yml +++ b/airflow_lappis/dags/dbt/ipea/dbt_project.yml @@ -19,16 +19,20 @@ clean-targets: models: ipea: + +database: analytics contratos_dbt: +materialized: table - +database: analytics +schema: contratos bronze: +materialized: incremental - pessoas: + pessoas_dbt: +materialized: table - +database: analytics +schema: pessoas + ted_dbt: + +materialized: table + +schema: ted + views: + +materialized: view on-run-start: - '{{create_udfs()}}' \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/ipea/macros/create_udfs.sql b/airflow_lappis/dags/dbt/ipea/macros/create_udfs.sql index a5db3274..dd230cf5 100644 --- a/airflow_lappis/dags/dbt/ipea/macros/create_udfs.sql +++ b/airflow_lappis/dags/dbt/ipea/macros/create_udfs.sql @@ -2,9 +2,9 @@ create schema if not exists {{ target.schema }}; - {{ create_f_parse_financial_value() }} - ; {{ create_f_parse_dates() }} ; + {{ create_f_format_nc() }} + ; {% endmacro %} diff --git a/airflow_lappis/dags/dbt/ipea/macros/parse_financial_value.sql b/airflow_lappis/dags/dbt/ipea/macros/parse_financial_value.sql new file mode 100644 index 00000000..86dfe861 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/macros/parse_financial_value.sql @@ -0,0 +1,19 @@ +{% macro parse_financial_value(column_name) %} + + case + when {{ column_name }} like '%NaN%' + then 0.00::numeric(15, 2) + when {{ column_name }} like '(%' + then + regexp_replace( + replace(coalesce({{ column_name }}, '0'), '.', ''), + '(\()?(\d+),(\d+)(\))?', + '-\2.\3' + )::numeric(15, 2) + else + replace( + replace(coalesce({{ column_name }}, '0'), '.', ''), ',', '.' + )::numeric(15, 2) + end + +{% endmacro %} diff --git a/airflow_lappis/dags/dbt/ipea/macros/udfs/f_format_nc.sql b/airflow_lappis/dags/dbt/ipea/macros/udfs/f_format_nc.sql new file mode 100644 index 00000000..f7a06c86 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/macros/udfs/f_format_nc.sql @@ -0,0 +1,19 @@ +{% macro create_f_format_nc() %} + create or replace function {{ target.schema }}.format_nc(in_text text) + returns text + as $$ + + with + + pre_process as ( + select left(in_text, 7) as prefix, + right(in_text, 4)::numeric as posfix + ) + + select concat(prefix, to_char(posfix, 'FM00000')) as result + from pre_process + + $$ + language sql + ; +{% endmacro %} diff --git a/airflow_lappis/dags/dbt/ipea/macros/udfs/f_parse_financial_value.sql b/airflow_lappis/dags/dbt/ipea/macros/udfs/f_parse_financial_value.sql deleted file mode 100644 index 7a319000..00000000 --- a/airflow_lappis/dags/dbt/ipea/macros/udfs/f_parse_financial_value.sql +++ /dev/null @@ -1,16 +0,0 @@ -{% macro create_f_parse_financial_value() %} - - create or replace function {{ target.schema }}.parse_number(in_text text) - returns numeric - as - $$ - select - case - when in_text like '%NaN%' then 0.00::numeric(15,2) - when in_text like '(%' then regexp_replace(replace(coalesce(in_text, '0'), '.', ''), '(\()?(\d+),(\d+)(\))?', '-\2.\3')::numeric(15,2) - else replace(replace(coalesce(in_text, '0'), '.', ''), ',', '.')::numeric(15,2) end as result -$$ - language sql - ; - -{% endmacro %} diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos_tesouro.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos_tesouro.sql index 42f05bc2..bf3dcdd2 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos_tesouro.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos_tesouro.sql @@ -1,3 +1,15 @@ +{{ + config( + unique_key=[ + "ne_ccor", + "natureza_despesa_detalhada", + "doc_observacao", + "ne_ccor_ano_emissao", + ], + incremental_strategy="merge", + ) +}} + with empenhos_raw as ( select @@ -15,24 +27,36 @@ with item_informacao::integer as ne_ccor_ano_emissao_1, regexp_replace(ne_num_processo, '[./-]', '') as ne_num_processo, -- Aplicando NULLIF e removendo parênteses antes de converter para NUMERIC - {{ target.schema }}.parse_number( - despesas_empenhadas_controle_empenho_saldo_moeda_origem - ) as despesas_empenhadas_saldo, - {{ target.schema }}.parse_number( - despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem - ) as despesas_empenhadas_movim_liquido, - {{ target.schema }}.parse_number( - despesas_liquidadas_controle_empenho_saldo_moeda_origem - ) as despesas_liquidadas_saldo, - {{ target.schema }}.parse_number( - despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem - ) as despesas_liquidadas_movim_liquido, - {{ target.schema }}.parse_number( - despesas_pagas_controle_empenho_saldo_moeda_origem - ) as despesas_pagas_saldo, - {{ target.schema }}.parse_number( - despesas_pagas_controle_empenho_movim_liquido_moeda_origem - ) as despesas_pagas_movim_liquido + {{ + parse_financial_value( + "despesas_empenhadas_controle_empenho_saldo_moeda_origem" + ) + }} as despesas_empenhadas_saldo, + {{ + parse_financial_value( + "despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem" + ) + }} as despesas_empenhadas_movim_liquido, + {{ + parse_financial_value( + "despesas_liquidadas_controle_empenho_saldo_moeda_origem" + ) + }} as despesas_liquidadas_saldo, + {{ + parse_financial_value( + "despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem" + ) + }} as despesas_liquidadas_movim_liquido, + {{ + parse_financial_value( + "despesas_pagas_controle_empenho_saldo_moeda_origem" + ) + }} as despesas_pagas_saldo, + {{ + parse_financial_value( + "despesas_pagas_controle_empenho_movim_liquido_moeda_origem" + ) + }} as despesas_pagas_movim_liquido from {{ source("siafi", "empenhos_tesouro") }} where ne_ccor != 'Total' ) diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/estagios.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/estagios.sql index 18aa1f1e..1938f436 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/estagios.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/estagios.sql @@ -1,3 +1,15 @@ +{{ + config( + unique_key=[ + "ne_ccor", + "doc_observacao", + "natureza_despesa_detalhada", + "mes_lancamento", + ], + incremental_strategy="merge", + ) +}} + -- Comentário: O erro indica que há valores com parênteses "(660000.00)" que não podem -- ser convertidos para double precision -- O erro está ocorrendo nas colunas de valores monetários, especificamente em: diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/estagios_mensal.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/estagios_mensal.sql index 087c5812..03a722b4 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/estagios_mensal.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/estagios_mensal.sql @@ -35,7 +35,7 @@ with ne, cnpj_cpf, info_complementar, - parse_date(mes_lancamento) as mes_lancamento, + {{ target.schema }}.parse_date(mes_lancamento) as mes_lancamento, min(num_processo) over (partition by ne) as num_processo, valor_empenhado, valor_liquidado, diff --git a/airflow_lappis/dags/dbt/ipea/models/sources.yml b/airflow_lappis/dags/dbt/ipea/models/sources.yml index 85583457..90326a7f 100644 --- a/airflow_lappis/dags/dbt/ipea/models/sources.yml +++ b/airflow_lappis/dags/dbt/ipea/models/sources.yml @@ -14,4 +14,13 @@ sources: tables: - name: empenhos_tesouro - name: estagios_tesouro + - name: ncs_tesouro + + - name: transfere_gov + schema: transfere_gov + tables: + - name: notas_de_credito + - name: planos_acao + - name: programacao_financeira + - name: programas diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/empenhos_w_plano_acao.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/empenhos_w_plano_acao.sql new file mode 100644 index 00000000..9e6827cb --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/empenhos_w_plano_acao.sql @@ -0,0 +1,31 @@ +with + empenhos_ids as ( + select + *, + -- Uma série de extrações que servirão de identificadores + right(ne_ccor, 12) as ne, + ( + regexp_match( + ne_ccor_descricao, + '(FERENCIA|NUMERO|Nº|TED|CRICAO|TRANSF.)(\s|^|-|)([0-9]{6}|1\w{5})(\s|$|\.|,|-|\/)' + ) + )[3] as num_transf, + {{ target.schema }}.format_nc( + regexp_substr(ne_ccor_descricao, '([0-9]{4}NC[0-9]+)') + ) as nc + from {{ ref("empenhos_tesouro") }} + ), + empenhos_filtrados as ( + select * from empenhos_ids where (nc != '') or (num_transf is not null) + ), + planos_de_acao as ( + select * from {{ ref("num_transf_n_plano_acao") }} where plano_acao is not null + ), + result_table as ( + select distinct * + from empenhos_filtrados + left join planos_de_acao using (num_transf) + ) -- + +select * +from result_table diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/notas_credito_w_plano_acao.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/notas_credito_w_plano_acao.sql new file mode 100644 index 00000000..313a16fe --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/notas_credito_w_plano_acao.sql @@ -0,0 +1,37 @@ +with + + raw_data as ( + select * + from {{ source("siafi", "ncs_tesouro") }} nt + where nc_transferencia != '-8' + ), + + planos_de_acao as ( + select distinct * + from {{ ref("num_transf_n_plano_acao") }} + where plano_acao is not null + ), + + result_table as ( + select + pda.plano_acao, + nc_transferencia, + right(nc, 12) as nc, + nc_fonte_recursos, + ptres, + nc_natureza_despesa, + nc_evento_descr, + -- aplica o sinal correto a depender do tipo de evento + case + when nc_evento in ('300302', '300308', '300311', '300083') + then (-1) * replace(nc_valor_linha, ',', '.')::numeric + else replace(nc_valor_linha, ',', '.')::numeric + end as nc_valor + from raw_data rd + left join planos_de_acao pda on rd.nc_transferencia = pda.num_transf + ) + +-- +select * +from result_table +order by 1, 2 diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/views/num_transf_n_plano_acao.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/views/num_transf_n_plano_acao.sql new file mode 100644 index 00000000..1b872140 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/views/num_transf_n_plano_acao.sql @@ -0,0 +1,26 @@ +{{ config(materialized="view") }} + +with + + nc_transfere_gov as ( + select distinct id_plano_acao, tx_numero_nota as nc, ndc.cd_ug_emitente_nota as ug + from {{ source("transfere_gov", "notas_de_credito") }} ndc + where ndc.tx_numero_nota is not null + ), + + nc_siafi as ( + select distinct + left(nc, 6) as ug, right(nc, 12) as nc, nt.nc_transferencia as num_transf + from {{ source("siafi", "ncs_tesouro") }} nt + where nc_transferencia != '-8' + ), + + result_table as ( + select distinct num_transf, id_plano_acao as plano_acao + from nc_siafi + left join nc_transfere_gov using (nc, ug) + ) + +-- +select * +from result_table diff --git a/requirements.txt b/requirements.txt index 3b4cef47..d525ddf1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,8 +35,8 @@ cron-descriptor==1.4.5 ; python_version >= "3.11" and python_version < "3.12" croniter==6.0.0 ; python_version >= "3.11" and python_version < "3.12" cryptography==44.0.2 ; python_version >= "3.11" and python_version < "3.12" daff==1.3.46 ; python_version >= "3.11" and python_version < "3.12" -dbt-adapters==1.14.4 ; python_version >= "3.11" and python_version < "3.12" -dbt-common==1.17.0 ; python_version >= "3.11" and python_version < "3.12" +dbt-adapters==1.14.3 ; python_version >= "3.11" and python_version < "3.12" +dbt-common==1.16.0 ; python_version >= "3.11" and python_version < "3.12" dbt-core==1.9.3 ; python_version >= "3.11" and python_version < "3.12" dbt-extractor==0.5.1 ; python_version >= "3.11" and python_version < "3.12" dbt-postgres==1.9.0 ; python_version >= "3.11" and python_version < "3.12" @@ -122,7 +122,7 @@ psutil==7.0.0 ; python_version >= "3.11" and python_version < "3.12" psycopg2-binary==2.9.10 ; python_version >= "3.11" and python_version < "3.12" pycparser==2.22 ; python_version >= "3.11" and python_version < "3.12" and platform_python_implementation != "PyPy" pydantic-core==2.33.0 ; python_version >= "3.11" and python_version < "3.12" -pydantic==2.11.1 ; python_version >= "3.11" and python_version < "3.12" +pydantic==2.11.0 ; python_version >= "3.11" and python_version < "3.12" pygments==2.19.1 ; python_version >= "3.11" and python_version < "3.12" pyjwt==2.10.1 ; python_version >= "3.11" and python_version < "3.12" python-daemon==3.1.2 ; python_version >= "3.11" and python_version < "3.12" From 1adce5968bf3d469a7b5ab985862ac2615eaa1ba Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Mon, 7 Apr 2025 11:33:21 +0000 Subject: [PATCH 042/317] Fix/email --- .../empenhos_tesouro_ingest_dag.py | 44 +++--- .../estagios_tesouro_ingest_dag.py | 6 +- .../contratos_dbt/bronze/empenhos_tesouro.sql | 58 +++----- airflow_lappis/plugins/cliente_email.py | 134 ++++++------------ 4 files changed, 86 insertions(+), 156 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py index ac425a14..5b6d5556 100755 --- a/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py @@ -1,12 +1,11 @@ from typing import Dict, Any, Optional - from airflow import DAG from airflow.operators.python import PythonOperator from airflow.models import Variable from datetime import datetime, timedelta import logging import json -from cliente_email import fetch_and_process_emails +from cliente_email import fetch_and_process_email from cliente_postgres import ClientPostgresDB from postgres_helpers import get_postgres_conn @@ -19,28 +18,29 @@ } COLUMN_MAPPING = { - 0: "ne_ccor", - 1: "ne_informacao_complementar", - 2: "ne_num_processo", - 3: "ne_ccor_descricao", - 4: "doc_observacao", - 5: "natureza_despesa", - 6: "natureza_despesa_1", - 7: "natureza_despesa_detalhada", - 8: "natureza_despesa_detalhada_1", + 0: "emissao_mes", + 1: "emissao_dia", + 2: "ne_ccor", + 3: "ne_num_processo", + 4: "ne_info_complementar", + 5: "ne_ccor_descricao", + 6: "doc_observacao", + 7: "natureza_despesa", + 8: "natureza_despesa_descricao", 9: "ne_ccor_favorecido", - 10: "ne_ccor_favorecido_1", + 10: "ne_ccor_favorecido_descricao", 11: "ne_ccor_ano_emissao", - 12: "item_informacao", - 13: "despesas_empenhadas_controle_empenho_saldo_moeda_origem", - 14: "despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem", - 15: "despesas_liquidadas_controle_empenho_saldo_moeda_origem", - 16: "despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem", - 17: "despesas_pagas_controle_empenho_saldo_moeda_origem", - 18: "despesas_pagas_controle_empenho_movim_liquido_moeda_origem", + 12: "ptres", + 13: "fonte_recursos_detalhada", + 14: "fonte_recursos_detalhada_descricao", + 15: "despesas_empenhadas", + 16: "despesas_liquidadas", + 17: "despesas_pagas", + 18: "restos_a_pagar_inscritos", + 19: "restos_a_pagar_pagos", } -EMAIL_SUBJECT = "consulta_por_execução_emp_liq_pago" +EMAIL_SUBJECT = "notas_de_empenhos_a_partir_de_2024" # Configurações da DAG @@ -63,10 +63,10 @@ def process_email_data(**context: Dict[str, Any]) -> Optional[str]: try: logging.info("Iniciando o processamento dos emails...") - csv_data = fetch_and_process_emails( + csv_data = fetch_and_process_email( + IMAP_SERVER, EMAIL, PASSWORD, - IMAP_SERVER, SENDER_EMAIL, EMAIL_SUBJECT, COLUMN_MAPPING, diff --git a/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py index 4712909d..3d2292ed 100755 --- a/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py @@ -8,7 +8,7 @@ import json from postgres_helpers import get_postgres_conn -from cliente_email import fetch_and_process_emails +from cliente_email import fetch_and_process_email from cliente_postgres import ClientPostgresDB # Configurações básicas da DAG @@ -66,7 +66,7 @@ def process_email_data(**context: Dict[str, Any]) -> Optional[str]: try: logging.info("Iniciando o processamento dos emails...") - csv_data = fetch_and_process_emails( + csv_data = fetch_and_process_email( EMAIL, PASSWORD, IMAP_SERVER, @@ -81,7 +81,7 @@ def process_email_data(**context: Dict[str, Any]) -> Optional[str]: logging.info( "CSV processado com sucesso. Dados encontrados: %s", len(csv_data) ) - return csv_data + return str(csv_data) if csv_data is not None else None except Exception as e: logging.error("Erro no processamento dos emails: %s", str(e)) raise diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos_tesouro.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos_tesouro.sql index bf3dcdd2..e378cd0b 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos_tesouro.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos_tesouro.sql @@ -2,9 +2,11 @@ config( unique_key=[ "ne_ccor", - "natureza_despesa_detalhada", + "natureza_despesa", "doc_observacao", "ne_ccor_ano_emissao", + "emissao_dia", + "emissao_mes", ], incremental_strategy="merge", ) @@ -13,50 +15,28 @@ with empenhos_raw as ( select + emissao_mes::text as emissao_mes, + emissao_dia::text as emissao_dia, ne_ccor::text as ne_ccor, - ne_informacao_complementar::text as ne_informacao_complementar, + regexp_replace(ne_num_processo, '[./-]', '') as ne_num_processo, + ne_info_complementar::text as ne_info_complementar, ne_ccor_descricao::text as ne_ccor_descricao, doc_observacao::text as doc_observacao, natureza_despesa::integer as natureza_despesa, - natureza_despesa_1::text as natureza_despesa_1, - natureza_despesa_detalhada::integer as natureza_despesa_detalhada, - natureza_despesa_detalhada_1::text as natureza_despesa_detalhada_1, + natureza_despesa_descricao::text as natureza_despesa_descricao, ne_ccor_favorecido::text as ne_ccor_favorecido, - ne_ccor_favorecido_1::text as ne_ccor_favorecido_1, + ne_ccor_favorecido_descricao::text as ne_ccor_favorecido_descricao, ne_ccor_ano_emissao::integer as ne_ccor_ano_emissao, - item_informacao::integer as ne_ccor_ano_emissao_1, - regexp_replace(ne_num_processo, '[./-]', '') as ne_num_processo, - -- Aplicando NULLIF e removendo parênteses antes de converter para NUMERIC - {{ - parse_financial_value( - "despesas_empenhadas_controle_empenho_saldo_moeda_origem" - ) - }} as despesas_empenhadas_saldo, - {{ - parse_financial_value( - "despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem" - ) - }} as despesas_empenhadas_movim_liquido, - {{ - parse_financial_value( - "despesas_liquidadas_controle_empenho_saldo_moeda_origem" - ) - }} as despesas_liquidadas_saldo, - {{ - parse_financial_value( - "despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem" - ) - }} as despesas_liquidadas_movim_liquido, - {{ - parse_financial_value( - "despesas_pagas_controle_empenho_saldo_moeda_origem" - ) - }} as despesas_pagas_saldo, - {{ - parse_financial_value( - "despesas_pagas_controle_empenho_movim_liquido_moeda_origem" - ) - }} as despesas_pagas_movim_liquido + ptres::text as ptres, + fonte_recursos_detalhada::text as fonte_recursos_detalhada, + fonte_recursos_detalhada_descricao::text + as fonte_recursos_detalhada_descricao, + {{ parse_financial_value("despesas_empenhadas") }} as despesas_empenhadas, + {{ parse_financial_value("despesas_liquidadas") }} as despesas_liquidadas, + {{ parse_financial_value("despesas_pagas") }} as despesas_pagas, + {{ parse_financial_value("restos_a_pagar_inscritos") }} + as restos_a_pagar_inscritos, + {{ parse_financial_value("restos_a_pagar_pagos") }} as restos_a_pagar_pagos from {{ source("siafi", "empenhos_tesouro") }} where ne_ccor != 'Total' ) diff --git a/airflow_lappis/plugins/cliente_email.py b/airflow_lappis/plugins/cliente_email.py index 0f079c12..21736041 100755 --- a/airflow_lappis/plugins/cliente_email.py +++ b/airflow_lappis/plugins/cliente_email.py @@ -1,14 +1,12 @@ import logging import io import zipfile -from typing import Optional -from typing_extensions import Buffer - +from typing import Optional, cast, List, Dict import pandas as pd -from imap_tools import MailBox, AND, MailMessage +from imap_tools import MailBox, AND import chardet -import pytz from datetime import datetime +import pytz # Configuração do log logging.basicConfig( @@ -16,109 +14,61 @@ ) -def format_csv(csv_data: str, column_mapping: dict) -> pd.DataFrame: +def format_csv(csv_data: str, column_mapping: Dict[int, str]) -> pd.DataFrame: """Formata um arquivo CSV conforme mapeamento de colunas.""" - try: - logging.info("Formatando CSV na memória...") - df = pd.read_csv(io.StringIO(csv_data), skiprows=5, header=None) - df.columns = pd.Index( - [column_mapping.get(i, f"col_{i}") for i in range(len(df.columns))] - ) - logging.info("CSV formatado com sucesso.") - return df - except Exception as e: - logging.error("Erro ao formatar CSV.") - raise ValueError(f"Erro ao formatar CSV: {e}") + df = pd.read_csv(io.StringIO(csv_data), skiprows=9, header=None) + column_names: List[str] = [ + column_mapping.get(i, f"col_{i}") for i in range(len(df.columns)) + ] + df.columns = pd.Index(column_names) + return df def extract_csv_from_zip( - zip_payload: Buffer, column_mapping: dict + zip_payload: bytes, column_mapping: dict ) -> Optional[pd.DataFrame]: - """Extrai e formata arquivos CSV de um arquivo ZIP.""" - try: - logging.info("Abrindo arquivo ZIP...") - with zipfile.ZipFile(io.BytesIO(zip_payload)) as zip_file: - for file_name in zip_file.namelist(): - if file_name.endswith(".csv"): - logging.info(f"Processando arquivo CSV: {file_name}") - with zip_file.open(file_name) as csv_file: - raw_data = csv_file.read() - encoding = chardet.detect(raw_data)["encoding"] - logging.info(f"Codificação detectada: {encoding}") - decoded_data = raw_data.decode(encoding) - return format_csv(decoded_data, column_mapping) - logging.warning("Nenhum arquivo CSV encontrado no ZIP.") - return None - except Exception as e: - logging.error("Erro ao processar arquivo ZIP.") - raise ValueError(f"Erro ao extrair CSV do ZIP: {e}") - - -def fetch_emails( - imap_server: str, email: str, password: str, sender_email: str, subject: str -) -> Optional[MailMessage]: - """Busca e-mails no servidor IMAP conforme critérios especificados.""" - local_tz = pytz.timezone("America/Sao_Paulo") - today = datetime.now(local_tz).date() - logging.info( - f"Conectando ao servidor IMAP para buscar e-mails de {sender_email} em {today}." - ) - - try: - with MailBox(imap_server).login(email, password) as mailbox: - emails: list[MailMessage] = list( - mailbox.fetch(AND(date=today, from_=sender_email)) - ) - for email_item in emails: - logging.info(f"E-mail encontrado: Assunto - '{email_item.subject}'") - if email_item.subject == subject: - return email_item - logging.warning("Nenhum e-mail com o assunto esperado encontrado.") - except Exception as e: - logging.error("Erro ao buscar e-mails.") - raise ConnectionError(f"Erro ao buscar e-mails: {e}") + """Extrai e formata o primeiro arquivo CSV encontrado em um ZIP.""" + with zipfile.ZipFile(io.BytesIO(zip_payload)) as zip_file: + for file_name in zip_file.namelist(): + if file_name.endswith(".csv"): + raw_data = zip_file.read(file_name) + encoding = chardet.detect(raw_data)["encoding"] + decoded_data = raw_data.decode(encoding) + return format_csv(decoded_data, column_mapping) return None -def process_email_attachments( - email_item: MailMessage, column_mapping: dict -) -> Optional[pd.DataFrame]: - """Processa os anexos ZIP de um e-mail e retorna o CSV formatado.""" - if not email_item: - logging.warning("Nenhum e-mail válido fornecido para processamento.") - return None - - for attachment in email_item.attachments: - if attachment.filename.endswith(".zip"): - logging.info(f"Anexo ZIP encontrado: {attachment.filename}. Processando...") - return extract_csv_from_zip(attachment.payload, column_mapping) - else: - logging.info(f"Anexo ignorado: {attachment.filename} (não é ZIP).") - - logging.warning("Nenhum anexo ZIP encontrado no e-mail.") +def fetch_email_with_zip( + imap_server: str, email: str, password: str, sender_email: str, subject: str +) -> Optional[bytes]: + """Busca o primeiro e-mail do dia atual com um anexo ZIP.""" + today = datetime.now(pytz.timezone("America/Sao_Paulo")).date() + with MailBox(imap_server).login(email, password) as mailbox: + for msg in mailbox.fetch(AND(date=today, from_=sender_email, subject=subject)): + for attachment in msg.attachments: + if attachment.filename.endswith(".zip"): + return cast(bytes, attachment.payload) return None -def fetch_and_process_emails( +def fetch_and_process_email( + imap_server: str, email: str, password: str, - imap_server: str, sender_email: str, subject: str, column_mapping: dict, ) -> Optional[str]: - """Orquestra a busca de e-mails, - processamento de anexos e retorno do CSV formatado.""" + """Busca e processa o primeiro e-mail com um ZIP contendo um CSV formatado.""" try: - email_item = fetch_emails(imap_server, email, password, sender_email, subject) - csv_data = process_email_attachments(email_item, column_mapping) - - if csv_data is not None: - logging.info("E-mail processado com sucesso. Retornando CSV formatado.") - return csv_data.to_csv(index=False) - else: - logging.info("Nenhum CSV processado.") - return None + zip_payload = fetch_email_with_zip( + imap_server, email, password, sender_email, subject + ) + if zip_payload: + csv_data = extract_csv_from_zip(zip_payload, column_mapping) + if csv_data is not None: + return csv_data.to_csv(index=False) + logging.warning("Nenhum CSV processado.") except Exception as e: - logging.error("Erro durante o processamento de e-mails.") - raise RuntimeError(f"Erro ao processar e-mails: {e}") + logging.error(f"Erro ao processar e-mails: {e}") + return None From c618d24a65ba7d133cb1dac6538f1dac23a75061 Mon Sep 17 00:00:00 2001 From: Mateus de Castro Date: Mon, 7 Apr 2025 17:35:18 +0000 Subject: [PATCH 043/317] Feat silver gold --- .../contratos_dbt/bronze/cronogramas.sql | 34 ++++++---- .../models/contratos_dbt/bronze/empenhos.sql | 4 +- .../models/contratos_dbt/bronze/faturas.sql | 4 +- .../gold/contratos_comparativo_mensal.sql | 3 +- .../gold/contratos_somatorio.sql | 17 +++++ .../silver/cronogramas_faturas_mensal.sql | 65 +++++++++++++++---- .../dbt/ipea/snapshots/contratos_snapshot.yml | 10 --- .../dbt/ipea/snapshots/tables_snapshot.yml | 30 +++++++++ 8 files changed, 127 insertions(+), 40 deletions(-) create mode 100644 airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_somatorio.sql delete mode 100644 airflow_lappis/dags/dbt/ipea/snapshots/contratos_snapshot.yml create mode 100644 airflow_lappis/dags/dbt/ipea/snapshots/tables_snapshot.yml diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/cronogramas.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/cronogramas.sql index 30f644c3..6cfb906f 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/cronogramas.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/cronogramas.sql @@ -1,15 +1,23 @@ {{ config(unikey_key="id") }} -select - id::integer as id, - contrato_id::integer as contrato_id, - tipo::text as tipo, - numero::text as numero, - receita_despesa::text as receita_despesa, - observacao::text as observacao, - mesref::integer as mesref, - anoref::integer as anoref, - retroativo::text as retroativo, - replace(replace(valor::text, '.', ''), ',', '.')::numeric(15, 2) as valor, - vencimento::date as vencimento -from {{ source("compras_gov", "cronograma") }} +with + cronogramas as ( + select + id::integer as id, + contrato_id::integer as contrato_id, + tipo::text as tipo, + numero::text as numero, + receita_despesa::text as receita_despesa, + observacao::text as observacao, + mesref::integer as mesref, + anoref::integer as anoref, + retroativo::text as retroativo, + replace(replace(valor::text, '.', ''), ',', '.')::numeric(15, 2) as valor, + vencimento::date as vencimento + from {{ source("compras_gov", "cronograma") }} + ) + + distinct_cronogramas as (select distinct * from cronogramas) + +select * +from distinct_cronogramas diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos.sql index fa631dbf..fcd4c01b 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos.sql @@ -1,4 +1,4 @@ -{{ config(unikey_key="id") }} +{{ config(materialized="table") }} with empenhos as ( @@ -50,7 +50,7 @@ with -- Retorna NULL se não for uma data válida then to_date(data_emissao::text, 'YYYY-MM-DD') end as data_emissao, - now() as inserted_at + now() as updated_at from {{ source("compras_gov", "empenhos") }} ) diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/faturas.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/faturas.sql index f6376fad..3889b68d 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/faturas.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/faturas.sql @@ -1,4 +1,4 @@ -{{ config(unikey_key="id") }} +{{ config(materialized="table") }} with faturas_raw as ( @@ -44,7 +44,7 @@ with upper(f.dados_empenho ->> 'numero_empenho') as numero_empenho, f.dados_empenho ->> 'valor_empenho' as valor_empenho, f.dados_empenho ->> 'subelemento' as subelemento, - now() as inserted_at + now() as updated_at from faturas_raw as f ) -- select * diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql index 998a1c17..dc0a21b6 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql @@ -8,7 +8,8 @@ select c.contrato_id, c.mes_ref, c.valor_cronograma as comprasgov_valor_cronograma, - c.valor_faturas as comprasgov_valor_faturas, + (c.valor_faturas_pagas + c.valor_faturas_pendentes) as comprasgov_valor_faturas, + c.saldo_contratual_disponivel as comprasgov_saldo_contratual_disponivel, s.valor_empenhado as siafi_valor_empenhado, s.valor_liquidado as siafi_valor_liquidado, s.valor_pago as siafi_valor_pago diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_somatorio.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_somatorio.sql new file mode 100644 index 00000000..e55798db --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_somatorio.sql @@ -0,0 +1,17 @@ +select + contrato_id, + sum(comprasgov_valor_cronograma) as total_cronograma, + sum(comprasgov_valor_faturas) as total_faturas, + sum(comprasgov_saldo_contratual_disponivel) as total_saldo_disponivel, + + -- Indicador de Orçamento a Executar: + sum( + case when comprasgov_valor_faturas = 0 then valor_cronograma else 0 end + ) as orcamento_a_executar + + sum(siafi_valor_empenhado) as total_empenhado, + sum(siafi_valor_liquidado) as total_liquidado, + sum(siafi_valor_pago) as total_pago + +from {{ ref("cronogramas_faturas_mensal") }} +group by contrato_id diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/cronogramas_faturas_mensal.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/cronogramas_faturas_mensal.sql index de9a94bf..859ca471 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/cronogramas_faturas_mensal.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/cronogramas_faturas_mensal.sql @@ -7,7 +7,36 @@ with order by contrato_id, vencimento ), - faturas_agg as ( + faturas_parsed as ( + select + contrato_id::integer as contrato_id, + emissao::date as emissao, + replace(replace(juros::text, '.', ''), ',', '.')::numeric(15, 2) as juros, + replace(replace(multa::text, '.', ''), ',', '.')::numeric(15, 2) as multa, + replace(replace(glosa::text, '.', ''), ',', '.')::numeric(15, 2) as glosa, + replace(replace(valorliquido::text, '.', ''), ',', '.')::numeric( + 15, 2 + ) as valorliquido, + situacao::text as situacao + from {{ source("compras_gov", "faturas") }} + ) + + faturas_pago as ( + select + contrato_id, + to_date( + split_part(emissao::text, '-', 1) -- verificar se o mês de emissão é o adequado para ser utilizada + || '-' + || split_part(emissao::text, '-', 2), + 'YYYY-MM' + ) as mes_ref, + sum(juros + multa + glosa + valorliquido) as valor_faturas_pagas + from faturas_parsed + where situacao = 'Pago' + group by 1, 2 + ), + + faturas_pendente as ( select contrato_id, to_date( @@ -16,20 +45,32 @@ with || split_part(emissao::text, '-', 2), 'YYYY-MM' ) as mes_ref, - sum(juros + multa + glosa + valorliquido) as valor_faturas - from {{ ref("faturas") }} + sum(juros + multa + glosa + valorliquido) as valor_faturas_pendentes + from faturas_parsed + where situacao = 'Pendente' group by 1, 2 ), joined_table as ( - select * from cronograma_agg left join faturas_agg using (contrato_id, mes_ref) + select * + from cronograma_agg + left join faturas_pago using (contrato_id, mes_ref) + left join faturas_pendente using (contrato_id, mes_ref) + ), + + joined_ajustado as ( + select + contrato_id::text, + mes_ref, + coalesce(valor_cronograma, 0) as valor_cronograma, + coalesce(valor_faturas_pagas, 0) as valor_faturas_pagas, + coalesce(valor_faturas_pendentes, 0) as valor_faturas_pendentes, + coalesce(valor_cronograma, 0) + - coalesce(valor_faturas_pagas, 0) + - coalesce(valor_faturas_pendentes, 0) as saldo_contratual_disponivel + from joined_table + order by contrato_id, mes_ref ) --- -select - contrato_id::text, - mes_ref, - coalesce(valor_cronograma, 0) as valor_cronograma, - coalesce(valor_faturas, 0) as valor_faturas -from joined_table -order by contrato_id, mes_ref +select * +from joined_ajustado diff --git a/airflow_lappis/dags/dbt/ipea/snapshots/contratos_snapshot.yml b/airflow_lappis/dags/dbt/ipea/snapshots/contratos_snapshot.yml deleted file mode 100644 index e637d5ec..00000000 --- a/airflow_lappis/dags/dbt/ipea/snapshots/contratos_snapshot.yml +++ /dev/null @@ -1,10 +0,0 @@ -snapshots: - - name: contratos_snapshot - relation: ref('contratos') - config: - schema: snapshots - database: analytics - unique_key: id - strategy: timestamp - updated_at: updated_at - dbt_valid_to_current: "to_timestamp('9999-12-31', 'YYYY-MM-DD')" \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/ipea/snapshots/tables_snapshot.yml b/airflow_lappis/dags/dbt/ipea/snapshots/tables_snapshot.yml new file mode 100644 index 00000000..a1cef75f --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/snapshots/tables_snapshot.yml @@ -0,0 +1,30 @@ +snapshots: + - name: contratos_snapshot + relation: ref('contratos') + config: + schema: snapshots + database: analytics + unique_key: id + strategy: timestamp + updated_at: updated_at + dbt_valid_to_current: "to_timestamp('9999-12-31', 'YYYY-MM-DD')" + + - name: faturas_snapshot + relation: ref('faturas') + config: + schema: snapshots + database: analytics + unique_key: id, id_empenho + strategy: timestamp + updated_at: updated_at + dbt_valid_to_current: "to_timestamp('9999-12-31', 'YYYY-MM-DD')" + + - name: empenhos_snapshot + relation: ref('empenhos') + config: + schema: snapshots + database: analytics + unique_key: id + strategy: timestamp + updated_at: updated_at + dbt_valid_to_current: "to_timestamp('9999-12-31', 'YYYY-MM-DD')" From 41b36b2b4499c033798d4338f6f1079f2fd4840b Mon Sep 17 00:00:00 2001 From: Joyce Date: Mon, 7 Apr 2025 17:59:12 -0300 Subject: [PATCH 044/317] fix(plugin): corrige schema --- airflow_lappis/plugins/cliente_postgres.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/airflow_lappis/plugins/cliente_postgres.py b/airflow_lappis/plugins/cliente_postgres.py index ca5e7f6b..f7279063 100755 --- a/airflow_lappis/plugins/cliente_postgres.py +++ b/airflow_lappis/plugins/cliente_postgres.py @@ -180,9 +180,9 @@ def execute_query(self, query: str) -> List[Tuple[Any, ...]]: ) return results - def get_contratos_ids(self) -> List[int]: + def get_contratos_ids(self, schema: str = "compras_gov") -> List[int]: """Extrai todos os IDs de contratos da tabela contratos.""" - query = "SELECT id FROM raw.contratos" + query = f"SELECT id FROM {schema}.contratos" with psycopg2.connect(self.conn_str) as conn: with conn.cursor() as cursor: From aaa8a3e32da12ac539be68015789b84f5461ce18 Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Tue, 8 Apr 2025 22:32:27 +0000 Subject: [PATCH 045/317] feaat(dags): adiciona dag de pfs e ajustas dags do email --- .../empenhos_tesouro_ingest_dag.py | 3 +- .../estagios_tesouro_ingest_dag.py | 7 +- .../dags/data_ingest/pf_tesouro_ingest_dag.py | 224 ++++++++++++++++++ airflow_lappis/plugins/cliente_email.py | 13 +- 4 files changed, 238 insertions(+), 9 deletions(-) create mode 100644 airflow_lappis/dags/data_ingest/pf_tesouro_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py index 5b6d5556..567ca265 100755 --- a/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py @@ -41,7 +41,7 @@ } EMAIL_SUBJECT = "notas_de_empenhos_a_partir_de_2024" - +SKIPROWS = 9 # Configurações da DAG with DAG( @@ -70,6 +70,7 @@ def process_email_data(**context: Dict[str, Any]) -> Optional[str]: SENDER_EMAIL, EMAIL_SUBJECT, COLUMN_MAPPING, + skiprows=SKIPROWS, ) if not csv_data: logging.warning("Nenhum e-mail encontrado com o assunto esperado.") diff --git a/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py index 3d2292ed..24c40005 100755 --- a/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py @@ -43,8 +43,8 @@ 20: "despesas_pagas_controle_empenho_movim_liquido_moeda_origem", } -EMAIL_SUBJECT = "consulta_por_execução_emp_liq_pago_mensal" - +EMAIL_SUBJECT = "consulta_por_execucao_emp_liq_pago_mensal" +SKIPROWS = 5 # Configurações da DAG with DAG( @@ -67,12 +67,13 @@ def process_email_data(**context: Dict[str, Any]) -> Optional[str]: try: logging.info("Iniciando o processamento dos emails...") csv_data = fetch_and_process_email( + IMAP_SERVER, EMAIL, PASSWORD, - IMAP_SERVER, SENDER_EMAIL, EMAIL_SUBJECT, COLUMN_MAPPING, + skiprows=SKIPROWS, ) if not csv_data: logging.warning("Nenhum e-mail encontrado com o assunto esperado.") diff --git a/airflow_lappis/dags/data_ingest/pf_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/pf_tesouro_ingest_dag.py new file mode 100644 index 00000000..ad193e9a --- /dev/null +++ b/airflow_lappis/dags/data_ingest/pf_tesouro_ingest_dag.py @@ -0,0 +1,224 @@ +from typing import Dict, Any, Optional, List, cast +from airflow import DAG +from airflow.operators.python import PythonOperator +from airflow.models import Variable +from datetime import datetime, timedelta +import logging +import json +from cliente_email import fetch_and_process_email +from cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn + +# Configurações básicas da DAG +default_args = { + "owner": "Davi", + "depends_on_past": False, + "retries": 1, + "retry_delay": timedelta(minutes=5), +} + +# Mapeamento das colunas para as programações financeiras +COLUMN_MAPPING = { + 0: "emissao_mes", + 1: "emissao_dia", + 2: "ug_emitente", + 3: "ug_emitente_descricao", + 4: "ug_favorecido", + 5: "ug_favorecido_descricao", + 6: "pf_evento", + 7: "pf_evento_descricao", + 8: "pf", + 9: "pf_acao", + 10: "pf_acao_descricao", + 11: "pf_fonte_recursos", + 12: "pf_fonte_recursos_descricao", + 13: "pf_vinculacao_pagamento", + 14: "pf_vinculacao_pagamento_descricao", + 15: "pf_categoria_gasto", + 16: "pf_recurso", + 17: "pf_recurso_descricao", + 18: "doc_observacao", + 19: "pf_valor_linha", +} + +# Assuntos dos emails a serem processados +EMAIL_SUBJECT_ENVIADAS = "programacoes_financeiras_enviadas_devolvidas_a_partir_de_2024" +EMAIL_SUBJECT_RECEBIDAS = "programacoes_financeiras_recebidas_a_partir_de_2024" +SKIPROWS = 3 + +# Configurações da DAG +with DAG( + dag_id="email_programacoes_financeiras_ingest", + default_args=default_args, + description="Processa anexos das PFs vindo de dois emails, formata e insere no db", + schedule_interval="0 13 * * 1-6", + start_date=datetime(2023, 12, 1), + catchup=False, +) as dag: + + def process_email_data_enviadas(**context: Dict[str, Any]) -> Optional[List[Dict]]: + """ + Função para processar os emails com programações financeiras enviadas. + """ + creds = json.loads(Variable.get("email_credentials")) + + EMAIL = creds["email"] + PASSWORD = creds["password"] + IMAP_SERVER = creds["imap_server"] + SENDER_EMAIL = creds["sender_email"] + + try: + logging.info( + "Iniciando o processamento dos emails de programações enviadas/devolvidas" + ) + csv_data = cast( + Optional[List[Dict[Any, Any]]], + fetch_and_process_email( + IMAP_SERVER, + EMAIL, + PASSWORD, + SENDER_EMAIL, + EMAIL_SUBJECT_ENVIADAS, + COLUMN_MAPPING, + skiprows=SKIPROWS, + ), + ) + if not csv_data: + logging.warning( + "Nenhum e-mail encontrado com o assunto de programações enviadas" + ) + return [] + + logging.info( + "CSV de PFs enviadas processado com sucesso. Dados encontrados: %s", + len(csv_data), + ) + return csv_data + except Exception as e: + logging.error( + "Erro no processamento dos emails de programações enviadas: %s", + str(e), + ) + raise + + def process_email_data_recebidas(**context: Dict[str, Any]) -> Optional[List[Dict]]: + """ + Função para processar os emails com programações financeiras recebidas. + """ + creds = json.loads(Variable.get("email_credentials")) + + EMAIL = creds["email"] + PASSWORD = creds["password"] + IMAP_SERVER = creds["imap_server"] + SENDER_EMAIL = creds["sender_email"] + + try: + logging.info( + "Iniciando o processamento dos emails de programações recebidas..." + ) + csv_data = cast( + Optional[List[Dict[Any, Any]]], + fetch_and_process_email( + IMAP_SERVER, + EMAIL, + PASSWORD, + SENDER_EMAIL, + EMAIL_SUBJECT_RECEBIDAS, + COLUMN_MAPPING, + skiprows=SKIPROWS, + ), + ) + if not csv_data: + logging.warning( + "Nenhum e-mail encontrado com o assunto de programações recebidas." + ) + return [] + + logging.info( + "CSV de PFs recebidas processado com sucesso. Dados encontrados: %s", + len(csv_data), + ) + return csv_data + except Exception as e: + logging.error( + "Erro no processamento dos emails de programações recebidas: %s", str(e) + ) + raise + + def combine_data(**context: Dict[str, Any]) -> List[Dict]: + """ + Função para combinar os dados dos dois emails. + """ + try: + task_instance: Any = context["ti"] + enviadas_data = ( + task_instance.xcom_pull(task_ids="process_emails_enviadas") or [] + ) + recebidas_data = ( + task_instance.xcom_pull(task_ids="process_emails_recebidas") or [] + ) + + combined_data = enviadas_data + recebidas_data + + logging.info(f"Dados combinados: {len(combined_data)} registros no total.") + return combined_data + except Exception as e: + logging.error(f"Erro ao combinar os dados: {str(e)}") + raise + + def insert_data_to_db(**context: Dict[str, Any]) -> None: + """ + Função para inserir os dados no banco de dados. + Os dados combinados são recuperados do XCom. + """ + try: + task_instance: Any = context["ti"] + combined_data = task_instance.xcom_pull(task_ids="combine_data") + + if not combined_data: + logging.warning("Nenhum dado para inserir no banco.") + return + + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + db.insert_csv_data(combined_data, "pf_tesouro", schema="siafi") + logging.info("Dados inseridos com sucesso no banco de dados.") + except Exception as e: + logging.error("Erro ao inserir dados no banco: %s", str(e)) + raise + + # Tarefa 1: Processar os e-mails de programações enviadas/devolvidas + process_emails_enviadas_task = PythonOperator( + task_id="process_emails_enviadas", + python_callable=process_email_data_enviadas, + provide_context=True, + ) + + # Tarefa 2: Processar os e-mails de programações recebidas + process_emails_recebidas_task = PythonOperator( + task_id="process_emails_recebidas", + python_callable=process_email_data_recebidas, + provide_context=True, + ) + + # Tarefa 3: Combinar os dados dos dois emails + combine_data_task = PythonOperator( + task_id="combine_data", + python_callable=combine_data, + provide_context=True, + ) + + # Tarefa 4: Inserir os dados no banco de dados + insert_to_db_task = PythonOperator( + task_id="insert_to_db", + python_callable=insert_data_to_db, + provide_context=True, + ) + + # Fluxo da DAG + ( + [process_emails_enviadas_task, process_emails_recebidas_task] + >> combine_data_task + >> insert_to_db_task + ) diff --git a/airflow_lappis/plugins/cliente_email.py b/airflow_lappis/plugins/cliente_email.py index 21736041..87f6a0a7 100755 --- a/airflow_lappis/plugins/cliente_email.py +++ b/airflow_lappis/plugins/cliente_email.py @@ -14,9 +14,11 @@ ) -def format_csv(csv_data: str, column_mapping: Dict[int, str]) -> pd.DataFrame: +def format_csv( + csv_data: str, column_mapping: Dict[int, str], skiprows: int +) -> pd.DataFrame: """Formata um arquivo CSV conforme mapeamento de colunas.""" - df = pd.read_csv(io.StringIO(csv_data), skiprows=9, header=None) + df = pd.read_csv(io.StringIO(csv_data), skiprows=skiprows, header=None) column_names: List[str] = [ column_mapping.get(i, f"col_{i}") for i in range(len(df.columns)) ] @@ -25,7 +27,7 @@ def format_csv(csv_data: str, column_mapping: Dict[int, str]) -> pd.DataFrame: def extract_csv_from_zip( - zip_payload: bytes, column_mapping: dict + zip_payload: bytes, column_mapping: dict, skiprows: int = 0 ) -> Optional[pd.DataFrame]: """Extrai e formata o primeiro arquivo CSV encontrado em um ZIP.""" with zipfile.ZipFile(io.BytesIO(zip_payload)) as zip_file: @@ -34,7 +36,7 @@ def extract_csv_from_zip( raw_data = zip_file.read(file_name) encoding = chardet.detect(raw_data)["encoding"] decoded_data = raw_data.decode(encoding) - return format_csv(decoded_data, column_mapping) + return format_csv(decoded_data, column_mapping, skiprows) return None @@ -58,6 +60,7 @@ def fetch_and_process_email( sender_email: str, subject: str, column_mapping: dict, + skiprows: int = 0, ) -> Optional[str]: """Busca e processa o primeiro e-mail com um ZIP contendo um CSV formatado.""" try: @@ -65,7 +68,7 @@ def fetch_and_process_email( imap_server, email, password, sender_email, subject ) if zip_payload: - csv_data = extract_csv_from_zip(zip_payload, column_mapping) + csv_data = extract_csv_from_zip(zip_payload, column_mapping, skiprows) if csv_data is not None: return csv_data.to_csv(index=False) logging.warning("Nenhum CSV processado.") From 163b0aacfd769566448d9b288ec2911bac6b782e Mon Sep 17 00:00:00 2001 From: VictorSzk Date: Wed, 9 Apr 2025 12:21:40 +0000 Subject: [PATCH 046/317] Fix/small sintax fix --- .../contratos_dbt/bronze/cronogramas.sql | 2 +- .../contratos_dbt/bronze/empenhos_tesouro.sql | 6 +- .../models/contratos_dbt/bronze/estagios.sql | 48 ++++++++----- .../contratos_dbt/gold/contratos_resumo.sql | 2 +- .../gold/contratos_somatorio.sql | 6 +- .../silver/contratos_empenhos.sql | 70 ++++++------------- .../silver/cronogramas_faturas_mensal.sql | 2 +- .../contratos_dbt/silver/estagios_mensal.sql | 15 ++-- .../dbt/ipea/snapshots/tables_snapshot.yml | 2 +- 9 files changed, 68 insertions(+), 85 deletions(-) diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/cronogramas.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/cronogramas.sql index 6cfb906f..e56e3702 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/cronogramas.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/cronogramas.sql @@ -15,7 +15,7 @@ with replace(replace(valor::text, '.', ''), ',', '.')::numeric(15, 2) as valor, vencimento::date as vencimento from {{ source("compras_gov", "cronograma") }} - ) + ), distinct_cronogramas as (select distinct * from cronogramas) diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos_tesouro.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos_tesouro.sql index e378cd0b..67829675 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos_tesouro.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos_tesouro.sql @@ -22,9 +22,9 @@ with ne_info_complementar::text as ne_info_complementar, ne_ccor_descricao::text as ne_ccor_descricao, doc_observacao::text as doc_observacao, - natureza_despesa::integer as natureza_despesa, + natureza_despesa::text as natureza_despesa, natureza_despesa_descricao::text as natureza_despesa_descricao, - ne_ccor_favorecido::text as ne_ccor_favorecido, + upper(ne_ccor_favorecido::text) as ne_ccor_favorecido, ne_ccor_favorecido_descricao::text as ne_ccor_favorecido_descricao, ne_ccor_ano_emissao::integer as ne_ccor_ano_emissao, ptres::text as ptres, @@ -38,7 +38,7 @@ with as restos_a_pagar_inscritos, {{ parse_financial_value("restos_a_pagar_pagos") }} as restos_a_pagar_pagos from {{ source("siafi", "empenhos_tesouro") }} - where ne_ccor != 'Total' + where ne_ccor_ano_emissao like '20%' ) select * diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/estagios.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/estagios.sql index 1938f436..c361cade 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/estagios.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/estagios.sql @@ -63,24 +63,36 @@ with case when ne_ccor_ano_emissao::text ~ '^\d+$' then ne_ccor_ano_emissao::integer end as ne_ccor_ano_emissao, - {{ target.schema }}.parse_number( - despesas_empenhadas_controle_empenho_saldo_moeda_origem - ) as despesas_empenhadas_controle_empenho_saldo_moeda_origem, - {{ target.schema }}.parse_number( - despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem - ) as despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem, - {{ target.schema }}.parse_number( - despesas_liquidadas_controle_empenho_saldo_moeda_origem - ) as despesas_liquidadas_controle_empenho_saldo_moeda_origem, - {{ target.schema }}.parse_number( - despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem - ) as despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem, - {{ target.schema }}.parse_number( - despesas_pagas_controle_empenho_saldo_moeda_origem - ) as despesas_pagas_controle_empenho_saldo_moeda_origem, - {{ target.schema }}.parse_number( - despesas_pagas_controle_empenho_movim_liquido_moeda_origem - ) as despesas_pagas_controle_empenho_movim_liquido_moeda_origem + {{ + parse_financial_value( + "despesas_empenhadas_controle_empenho_saldo_moeda_origem" + ) + }} as despesas_empenhadas_controle_empenho_saldo_moeda_origem, + {{ + parse_financial_value( + "despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem" + ) + }} as despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem, + {{ + parse_financial_value( + "despesas_liquidadas_controle_empenho_saldo_moeda_origem" + ) + }} as despesas_liquidadas_controle_empenho_saldo_moeda_origem, + {{ + parse_financial_value( + "despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem" + ) + }} as despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem, + {{ + parse_financial_value( + "despesas_pagas_controle_empenho_saldo_moeda_origem" + ) + }} as despesas_pagas_controle_empenho_saldo_moeda_origem, + {{ + parse_financial_value( + "despesas_pagas_controle_empenho_movim_liquido_moeda_origem" + ) + }} as despesas_pagas_controle_empenho_movim_liquido_moeda_origem from {{ source("siafi", "estagios_tesouro") }} ) diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_resumo.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_resumo.sql index 5c32781d..9499356c 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_resumo.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_resumo.sql @@ -1,7 +1,7 @@ with valores_pagos_contratos as ( - select contrato_id as id, sum(despesas_pagas_movim_liquido) as despesas_pagas + select contrato_id as id, sum(despesas_pagas) as despesas_pagas from {{ ref("contratos_empenhos") }} where contrato_id is not null group by contrato_id diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_somatorio.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_somatorio.sql index e55798db..d1c913a6 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_somatorio.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_somatorio.sql @@ -6,12 +6,12 @@ select -- Indicador de Orçamento a Executar: sum( - case when comprasgov_valor_faturas = 0 then valor_cronograma else 0 end - ) as orcamento_a_executar + case when comprasgov_valor_faturas = 0 then comprasgov_valor_cronograma else 0 end + ) as orcamento_a_executar, sum(siafi_valor_empenhado) as total_empenhado, sum(siafi_valor_liquidado) as total_liquidado, sum(siafi_valor_pago) as total_pago -from {{ ref("cronogramas_faturas_mensal") }} +from {{ ref("contratos_comparativo_mensal") }} group by contrato_id diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_empenhos.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_empenhos.sql index fafe8163..6f9ef137 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_empenhos.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_empenhos.sql @@ -38,24 +38,17 @@ with contrato_id, coalesce(ne_transformed, ne) as ne_transformed, ne_ccor, - ne_informacao_complementar, + ne_info_complementar, ne_num_processo, ne_ccor_descricao, doc_observacao, natureza_despesa, - natureza_despesa_1, - natureza_despesa_detalhada, - natureza_despesa_detalhada_1, + natureza_despesa_descricao, ne_ccor_favorecido, - ne_ccor_favorecido_1, ne_ccor_ano_emissao, - ne_ccor_ano_emissao_1, - despesas_empenhadas_saldo, - despesas_empenhadas_movim_liquido, - despesas_liquidadas_saldo, - despesas_liquidadas_movim_liquido, - despesas_pagas_saldo, - despesas_pagas_movim_liquido + despesas_empenhadas, + despesas_liquidadas, + despesas_pagas from full_join where origem = 'both' or origem = 'left only' -- contrato_id nulo significa lacuna no lado esquerdo do RIGHT JOIN, @@ -95,24 +88,17 @@ with contrato_id, ne_transformed, ne_ccor, - ne_informacao_complementar, + ne_info_complementar, ne_num_processo, ne_ccor_descricao, doc_observacao, natureza_despesa, - natureza_despesa_1, - natureza_despesa_detalhada, - natureza_despesa_detalhada_1, + natureza_despesa_descricao, ne_ccor_favorecido, - ne_ccor_favorecido_1, ne_ccor_ano_emissao, - ne_ccor_ano_emissao_1, - despesas_empenhadas_saldo, - despesas_empenhadas_movim_liquido, - despesas_liquidadas_saldo, - despesas_liquidadas_movim_liquido, - despesas_pagas_saldo, - despesas_pagas_movim_liquido + despesas_empenhadas, + despesas_liquidadas, + despesas_pagas from juncao_processo -- WHERE origem = 'both' where contrato_id is not null @@ -146,24 +132,17 @@ with contrato_id, ne_transformed, ne_ccor, - ne_informacao_complementar, + ne_info_complementar, ne_num_processo, ne_ccor_descricao, doc_observacao, natureza_despesa, - natureza_despesa_1, - natureza_despesa_detalhada, - natureza_despesa_detalhada_1, + natureza_despesa_descricao, ne_ccor_favorecido, - ne_ccor_favorecido_1, ne_ccor_ano_emissao, - ne_ccor_ano_emissao_1, - despesas_empenhadas_saldo, - despesas_empenhadas_movim_liquido, - despesas_liquidadas_saldo, - despesas_liquidadas_movim_liquido, - despesas_pagas_saldo, - despesas_pagas_movim_liquido + despesas_empenhadas, + despesas_liquidadas, + despesas_pagas from juncao_cnpjs where contrato_id is not null ), @@ -173,7 +152,7 @@ with select *, -- garantir que ambos os lados estão no mesmo formato - substring(ne_informacao_complementar from '^([0-9]+) -') as info_complementar + substring(ne_info_complementar from '^([0-9]+) -') as info_complementar from empenhos_restantes_2 where ne_ccor not in (select ne_ccor from resultado_3) ), @@ -195,24 +174,17 @@ with contrato_id, ne_transformed, ne_ccor, - ne_informacao_complementar, + ne_info_complementar, ne_num_processo, ne_ccor_descricao, doc_observacao, natureza_despesa, - natureza_despesa_1, - natureza_despesa_detalhada, - natureza_despesa_detalhada_1, + natureza_despesa_descricao, ne_ccor_favorecido, - ne_ccor_favorecido_1, ne_ccor_ano_emissao, - ne_ccor_ano_emissao_1, - despesas_empenhadas_saldo, - despesas_empenhadas_movim_liquido, - despesas_liquidadas_saldo, - despesas_liquidadas_movim_liquido, - despesas_pagas_saldo, - despesas_pagas_movim_liquido + despesas_empenhadas, + despesas_liquidadas, + despesas_pagas from juncao_info_complementar where contrato_id is not null ), diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/cronogramas_faturas_mensal.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/cronogramas_faturas_mensal.sql index 859ca471..444370f9 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/cronogramas_faturas_mensal.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/cronogramas_faturas_mensal.sql @@ -19,7 +19,7 @@ with ) as valorliquido, situacao::text as situacao from {{ source("compras_gov", "faturas") }} - ) + ), faturas_pago as ( select diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/estagios_mensal.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/estagios_mensal.sql index 03a722b4..9530dca5 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/estagios_mensal.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/estagios_mensal.sql @@ -3,16 +3,15 @@ with parsed_estagios as ( select right(ne_ccor, 12) as ne, - mes_lancamento, + emissao_mes as mes_lancamento, ne_ccor_favorecido as cnpj_cpf, - substring(ne_informacao_complementar, '(^[0-9]+)') as info_complementar, + substring(ne_info_complementar, '(^[0-9]+)') as info_complementar, ne_num_processo, - despesas_empenhadas_controle_empenho_saldo_moeda_origem valor_empenhado, - despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem - as valor_liquidado, - despesas_pagas_controle_empenho_movim_liquido_moeda_origem as valor_pago - from {{ ref("estagios") }} - where true and ne_ccor != 'Total' and mes_lancamento not like '01%' + despesas_empenhadas as valor_empenhado, + despesas_liquidadas as valor_liquidado, + despesas_pagas as valor_pago + from {{ ref("empenhos_tesouro") }} + where true and ne_ccor != 'Total' and emissao_mes not like '00%' ), grouped_estagios as ( diff --git a/airflow_lappis/dags/dbt/ipea/snapshots/tables_snapshot.yml b/airflow_lappis/dags/dbt/ipea/snapshots/tables_snapshot.yml index a1cef75f..bedc4e47 100644 --- a/airflow_lappis/dags/dbt/ipea/snapshots/tables_snapshot.yml +++ b/airflow_lappis/dags/dbt/ipea/snapshots/tables_snapshot.yml @@ -14,7 +14,7 @@ snapshots: config: schema: snapshots database: analytics - unique_key: id, id_empenho + unique_key: [id, id_empenho] strategy: timestamp updated_at: updated_at dbt_valid_to_current: "to_timestamp('9999-12-31', 'YYYY-MM-DD')" From 61252874040944fb332f730dfb5111004fcd6618 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Wed, 9 Apr 2025 14:51:32 -0300 Subject: [PATCH 047/317] feat(column): adiciona possibilidade de mapeamento nulo para colunas --- .../dags/data_ingest/pf_tesouro_ingest_dag.py | 2 +- airflow_lappis/plugins/cliente_email.py | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/pf_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/pf_tesouro_ingest_dag.py index ad193e9a..e757437c 100644 --- a/airflow_lappis/dags/data_ingest/pf_tesouro_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/pf_tesouro_ingest_dag.py @@ -124,7 +124,7 @@ def process_email_data_recebidas(**context: Dict[str, Any]) -> Optional[List[Dic PASSWORD, SENDER_EMAIL, EMAIL_SUBJECT_RECEBIDAS, - COLUMN_MAPPING, + column_mapping=None, skiprows=SKIPROWS, ), ) diff --git a/airflow_lappis/plugins/cliente_email.py b/airflow_lappis/plugins/cliente_email.py index 87f6a0a7..b85ded3d 100755 --- a/airflow_lappis/plugins/cliente_email.py +++ b/airflow_lappis/plugins/cliente_email.py @@ -15,14 +15,17 @@ def format_csv( - csv_data: str, column_mapping: Dict[int, str], skiprows: int + csv_data: str, column_mapping: Optional[Dict[int, str]], skiprows: int ) -> pd.DataFrame: """Formata um arquivo CSV conforme mapeamento de colunas.""" - df = pd.read_csv(io.StringIO(csv_data), skiprows=skiprows, header=None) - column_names: List[str] = [ - column_mapping.get(i, f"col_{i}") for i in range(len(df.columns)) - ] - df.columns = pd.Index(column_names) + if column_mapping: + df = pd.read_csv(io.StringIO(csv_data), skiprows=skiprows, header=None) + column_names: List[str] = [ + column_mapping.get(i, f"col_{i}") for i in range(len(df.columns)) + ] + df.columns = pd.Index(column_names) + else: + df = pd.read_csv(io.StringIO(csv_data), skiprows=skiprows, header=0) return df From 91889b8b30850145338b55a844be79bbb50538f8 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Wed, 9 Apr 2025 17:58:34 -0300 Subject: [PATCH 048/317] fix(postgres): ajusta conexao postgres de unidade organizacional --- .../unidade_organizacional_ingest_dag.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py b/airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py index 324f1c0b..f609a658 100755 --- a/airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py @@ -1,26 +1,11 @@ import logging from airflow.decorators import dag, task -from airflow.providers.postgres.hooks.postgres import PostgresHook from datetime import datetime, timedelta +from postgres_helpers import get_postgres_conn from cliente_estrutura import ClienteEstrutura from cliente_postgres import ClientPostgresDB -def get_postgres_conn() -> str: - hook = PostgresHook(postgres_conn_id="postgres_default") - conn = hook.connection - port = conn.port - schema = conn.schema - logging.info( - f"[unidade_organizacional_ingest_dag.py] Obtained PostgreSQL connection: " - f"dbname={schema}, user={conn.login}, host={conn.host}, port={port}" - ) - return ( - f"dbname={schema} user={conn.login} password={conn.password} " - f"host={conn.host} port={port}" - ) - - @dag( schedule_interval="@daily", start_date=datetime(2023, 1, 1), From 51d850deb2e7d489bda5e123a3c4b970556a7a3e Mon Sep 17 00:00:00 2001 From: mat054 Date: Fri, 11 Apr 2025 07:53:49 -0300 Subject: [PATCH 049/317] snapshot de cronogramas --- .../ipea/models/contratos_dbt/bronze/cronogramas.sql | 5 +++-- .../dags/dbt/ipea/snapshots/tables_snapshot.yml | 10 ++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/cronogramas.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/cronogramas.sql index e56e3702..cd784c88 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/cronogramas.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/cronogramas.sql @@ -1,4 +1,4 @@ -{{ config(unikey_key="id") }} +{{ config(materialized="table") }} with cronogramas as ( @@ -13,7 +13,8 @@ with anoref::integer as anoref, retroativo::text as retroativo, replace(replace(valor::text, '.', ''), ',', '.')::numeric(15, 2) as valor, - vencimento::date as vencimento + vencimento::date as vencimento, + now() as updated_at from {{ source("compras_gov", "cronograma") }} ), diff --git a/airflow_lappis/dags/dbt/ipea/snapshots/tables_snapshot.yml b/airflow_lappis/dags/dbt/ipea/snapshots/tables_snapshot.yml index bedc4e47..7c8b5400 100644 --- a/airflow_lappis/dags/dbt/ipea/snapshots/tables_snapshot.yml +++ b/airflow_lappis/dags/dbt/ipea/snapshots/tables_snapshot.yml @@ -28,3 +28,13 @@ snapshots: strategy: timestamp updated_at: updated_at dbt_valid_to_current: "to_timestamp('9999-12-31', 'YYYY-MM-DD')" + + - name: cronogramas_snapshot + relation: ref('cronogramas') + config: + schema: snapshots + database: analytics + unique_key: id + strategy: timestamp + updated_at: updated_at + dbt_valid_to_current: "to_timestamp('9999-12-31', 'YYYY-MM-DD')" \ No newline at end of file From 9acb605e542ecf53f319590d1fb0c3bb5b4eeabb Mon Sep 17 00:00:00 2001 From: VictorSzk Date: Fri, 11 Apr 2025 16:13:21 +0000 Subject: [PATCH 050/317] Feat/fix time gaps --- airflow_lappis/dags/dbt/ipea/dbt_project.yml | 4 ++ .../gold/contratos_comparativo_mensal.sql | 46 ++++++++++++++----- .../{bronze => views}/identificadores.sql | 2 - .../views/preenchimento_meses.sql | 17 +++++++ 4 files changed, 55 insertions(+), 14 deletions(-) rename airflow_lappis/dags/dbt/ipea/models/contratos_dbt/{bronze => views}/identificadores.sql (98%) create mode 100644 airflow_lappis/dags/dbt/ipea/models/contratos_dbt/views/preenchimento_meses.sql diff --git a/airflow_lappis/dags/dbt/ipea/dbt_project.yml b/airflow_lappis/dags/dbt/ipea/dbt_project.yml index 7fea0776..479b5393 100755 --- a/airflow_lappis/dags/dbt/ipea/dbt_project.yml +++ b/airflow_lappis/dags/dbt/ipea/dbt_project.yml @@ -25,9 +25,13 @@ models: +schema: contratos bronze: +materialized: incremental + views: + +materialized: view pessoas_dbt: +materialized: table +schema: pessoas + views: + +materialized: view ted_dbt: +materialized: table +schema: ted diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql index dc0a21b6..64734e6c 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql @@ -2,17 +2,39 @@ with siafi_data as (select * from {{ ref("contratos_estagios") }}), - compras_gov_data as (select * from {{ ref("cronogramas_faturas_mensal") }}) + compras_gov_data as (select * from {{ ref("cronogramas_faturas_mensal") }}), + partial_result as ( + select + c.contrato_id, + c.mes_ref, + c.valor_cronograma as comprasgov_valor_cronograma, + ( + c.valor_faturas_pagas + c.valor_faturas_pendentes + ) as comprasgov_valor_faturas, + c.saldo_contratual_disponivel as comprasgov_saldo_contratual_disponivel, + s.valor_empenhado as siafi_valor_empenhado, + s.valor_liquidado as siafi_valor_liquidado, + s.valor_pago as siafi_valor_pago + from compras_gov_data as c + left join + siafi_data as s + on c.contrato_id = s.contrato_id + and c.mes_ref = s.mes_lancamento + + ), + + preenchimento as (select contrato_id, mes_ref from {{ ref("preenchimento_meses") }}) + +-- select - c.contrato_id, - c.mes_ref, - c.valor_cronograma as comprasgov_valor_cronograma, - (c.valor_faturas_pagas + c.valor_faturas_pendentes) as comprasgov_valor_faturas, - c.saldo_contratual_disponivel as comprasgov_saldo_contratual_disponivel, - s.valor_empenhado as siafi_valor_empenhado, - s.valor_liquidado as siafi_valor_liquidado, - s.valor_pago as siafi_valor_pago -from compras_gov_data as c -left join - siafi_data as s on c.contrato_id = s.contrato_id and c.mes_ref = s.mes_lancamento + contrato_id, + mes_ref, + comprasgov_valor_cronograma, + comprasgov_valor_faturas, + comprasgov_saldo_contratual_disponivel, + siafi_valor_empenhado, + siafi_valor_liquidado, + siafi_valor_pago +from partial_result +full join preenchimento using (contrato_id, mes_ref) diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/identificadores.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/views/identificadores.sql similarity index 98% rename from airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/identificadores.sql rename to airflow_lappis/dags/dbt/ipea/models/contratos_dbt/views/identificadores.sql index 4a897528..f0301d2f 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/identificadores.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/views/identificadores.sql @@ -1,5 +1,3 @@ -{{ config(materialized="table") }} - with ids_from_empenhos as ( diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/views/preenchimento_meses.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/views/preenchimento_meses.sql new file mode 100644 index 00000000..f1f13297 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/views/preenchimento_meses.sql @@ -0,0 +1,17 @@ +-- Essa view será usada para preencher todos os gaps temporais +-- em tabelas gold com o propósito de eliminar decontinuidades +-- nas visualizações em linha +with + + contractos_lista as ( + select contrato_id, min(mes_ref) as min_mes, max(mes_ref) as max_mes + from {{ ref("cronogramas_faturas_mensal") }} + group by contrato_id + ), + + meses_lista as (select distinct mes_ref from {{ ref("cronogramas_faturas_mensal") }}) + +-- +select c.contrato_id, m.mes_ref +from contractos_lista c +left join meses_lista m on (c.min_mes <= m.mes_ref) and (c.max_mes >= m.mes_ref) From 954f8187dc6cdf4b7120426f67a21b2508f6afd9 Mon Sep 17 00:00:00 2001 From: VictorSzk Date: Sat, 12 Apr 2025 13:36:12 +0000 Subject: [PATCH 051/317] Fix/join gold --- .../gold/contratos_comparativo_mensal.sql | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql index 64734e6c..0522cdb6 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql @@ -1,13 +1,15 @@ with - siafi_data as (select * from {{ ref("contratos_estagios") }}), + siafi_data as ( + select *, mes_lancamento as mes_ref from {{ ref("contratos_estagios") }} + ), compras_gov_data as (select * from {{ ref("cronogramas_faturas_mensal") }}), partial_result as ( select - c.contrato_id, - c.mes_ref, + contrato_id, + mes_ref, c.valor_cronograma as comprasgov_valor_cronograma, ( c.valor_faturas_pagas + c.valor_faturas_pendentes @@ -17,10 +19,7 @@ with s.valor_liquidado as siafi_valor_liquidado, s.valor_pago as siafi_valor_pago from compras_gov_data as c - left join - siafi_data as s - on c.contrato_id = s.contrato_id - and c.mes_ref = s.mes_lancamento + full join siafi_data as s using (contrato_id, mes_ref) ), From 1a92f06fb1765a0e2941708fff35886aa063c6ea Mon Sep 17 00:00:00 2001 From: arthrok Date: Mon, 14 Apr 2025 10:25:05 -0300 Subject: [PATCH 052/317] feat(dbt): parametriza profiles.yml com variaveis de ambiente --- airflow_lappis/dags/dbt/ipea/profiles.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/airflow_lappis/dags/dbt/ipea/profiles.yml b/airflow_lappis/dags/dbt/ipea/profiles.yml index 90616362..17f14e26 100755 --- a/airflow_lappis/dags/dbt/ipea/profiles.yml +++ b/airflow_lappis/dags/dbt/ipea/profiles.yml @@ -3,10 +3,9 @@ ipea: outputs: prod: type: postgres - host: 10.0.0.73 - user: analytics - password: xQ3hxNJThsVx3WqEp6Yr0hhtptSMbmbFWyL2 - port: 5432 - dbname: analytics - schema: ipea - + host: "{{ env_var('DB_DW_HOST', 'postgres') }}" + user: "{{ env_var('DB_DW_USER', 'postgres_dw') }}" + password: "{{ env_var('DB_DW_PASSWORD', 'postgres_dw') }}" + port: "{{ env_var('DB_DW_PORT', '5432') | int }}" + dbname: "{{ env_var('DB_DW_DBNAME', 'data_warehouse') }}" + schema: "{{ env_var('DB_DW_SCHEMA', 'ipea') }}" \ No newline at end of file From e5ec60828d3ae5dac8ad19091afa2bea4dda05c3 Mon Sep 17 00:00:00 2001 From: VictorSzk Date: Tue, 15 Apr 2025 20:52:01 +0000 Subject: [PATCH 053/317] Ted models --- .../dbt/ipea/macros/udfs/f_parse_dates.sql | 2 +- .../dags/dbt/ipea/models/sources.yml | 1 + .../ipea/models/ted_dbt/bronze/pf_tesouro.sql | 30 +++++++++++++++++ ...plano_acao.sql => empenhos_plano_acao.sql} | 0 ...ito_w_plano_acao.sql => nc_plano_acao.sql} | 0 .../models/ted_dbt/silver/pf_plano_acao.sql | 33 +++++++++++++++++++ 6 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/pf_tesouro.sql rename airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/{empenhos_w_plano_acao.sql => empenhos_plano_acao.sql} (100%) rename airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/{notas_credito_w_plano_acao.sql => nc_plano_acao.sql} (100%) create mode 100644 airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/pf_plano_acao.sql diff --git a/airflow_lappis/dags/dbt/ipea/macros/udfs/f_parse_dates.sql b/airflow_lappis/dags/dbt/ipea/macros/udfs/f_parse_dates.sql index 5297789e..3fd8693e 100644 --- a/airflow_lappis/dags/dbt/ipea/macros/udfs/f_parse_dates.sql +++ b/airflow_lappis/dags/dbt/ipea/macros/udfs/f_parse_dates.sql @@ -35,7 +35,7 @@ ) select - to_date(ano::numeric - 1 || '-' || '12', 'YYYY-MM') + (mes_num || ' months')::interval as result + (to_date(ano::numeric - 1 || '-' || '12', 'YYYY-MM') + (mes_num || ' months')::interval)::date as result from fixed_month $$ language sql diff --git a/airflow_lappis/dags/dbt/ipea/models/sources.yml b/airflow_lappis/dags/dbt/ipea/models/sources.yml index 90326a7f..9ad13553 100644 --- a/airflow_lappis/dags/dbt/ipea/models/sources.yml +++ b/airflow_lappis/dags/dbt/ipea/models/sources.yml @@ -15,6 +15,7 @@ sources: - name: empenhos_tesouro - name: estagios_tesouro - name: ncs_tesouro + - name: pf_tesouro - name: transfere_gov schema: transfere_gov diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/pf_tesouro.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/pf_tesouro.sql new file mode 100644 index 00000000..90e5e811 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/pf_tesouro.sql @@ -0,0 +1,30 @@ +with + + programacoes_financeira as ( + select + {{ target.schema }}.parse_date(emissao_mes) as emissao_mes, + to_date(emissao_dia, 'DD/MM/YYYY') as emissao_dia, + ug_emitente, + ug_emitente_descricao, + ug_favorecido, + ug_favorecido_descricao, + pf_evento, + pf_evento_descricao, + right(pf, 12) as pf, + pf_acao, + pf_acao_descricao, + pf_fonte_recursos, + pf_fonte_recursos_descricao, + pf_vinculacao_pagamento, + pf_vinculacao_pagamento_descricao, + pf_categoria_gasto, + pf_recurso, + pf_recurso_descricao, + doc_observacao, + replace(pf_valor_linha, ',', '.')::numeric(15, 2) as pf_valor_linha + from {{ source("siafi", "pf_tesouro") }} + ) + +-- +select * +from programacoes_financeira diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/empenhos_w_plano_acao.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/empenhos_plano_acao.sql similarity index 100% rename from airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/empenhos_w_plano_acao.sql rename to airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/empenhos_plano_acao.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/notas_credito_w_plano_acao.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/nc_plano_acao.sql similarity index 100% rename from airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/notas_credito_w_plano_acao.sql rename to airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/nc_plano_acao.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/pf_plano_acao.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/pf_plano_acao.sql new file mode 100644 index 00000000..ad90dee3 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/pf_plano_acao.sql @@ -0,0 +1,33 @@ +with + + programacoes_financeira as ( + select + pf, + emissao_mes, + emissao_dia, + ug_emitente, + ug_favorecido, + pf_evento, + pf_evento_descricao, + substring(pf_acao_descricao, '(\w+) ') as pf_acao, + pf_valor_linha + from {{ ref("pf_tesouro") }} + ), + + pf_transfere_gov as ( + select + id_plano_acao as plano_acao, + ug_emitente_programacao as ug_emitente, + tx_numero_programacao as pf + from {{ source("transfere_gov", "programacao_financeira") }} + ), + + joined_table as ( + select * + from programacoes_financeira + inner join pf_transfere_gov using (pf, ug_emitente) + ) + +-- +select * +from joined_table From bfb75c1039c9c88836ff967e0349670a222e1b73 Mon Sep 17 00:00:00 2001 From: mat054 Date: Wed, 16 Apr 2025 22:29:44 -0300 Subject: [PATCH 054/317] alteracao ingestao cronogramas --- airflow_lappis/dags/data_ingest/cronograma_ingest_dag.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/airflow_lappis/dags/data_ingest/cronograma_ingest_dag.py b/airflow_lappis/dags/data_ingest/cronograma_ingest_dag.py index 41913288..ca6e2842 100755 --- a/airflow_lappis/dags/data_ingest/cronograma_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/cronograma_ingest_dag.py @@ -27,6 +27,11 @@ def fetch_cronogramas() -> None: db = ClientPostgresDB(postgres_conn_str) contratos_ids = db.get_contratos_ids() + # Drop the existing cronograma table before inserting new data + logging.info("[cronograma_ingest_dag.py] Dropping existing cronograma table") + db.drop_table_if_exists("cronograma", schema="compras_gov") + logging.info("[cronograma_ingest_dag.py] Table dropped successfully") + for contrato_id in contratos_ids: logging.info( f"[cronograma_ingest_dag.py] Fetching cronograma for contrato ID: " From 7836f6fb669d8d32a9a0001c754e5417efae1bd2 Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Thu, 17 Apr 2025 20:12:30 +0000 Subject: [PATCH 055/317] feat(insert): ajusta o insert das tabelas do tesouro --- .../empenhos_tesouro_ingest_dag.py | 23 +++++++- .../dags/data_ingest/pf_tesouro_ingest_dag.py | 30 ++++++++++- airflow_lappis/plugins/cliente_postgres.py | 53 +++++++++++++++++++ 3 files changed, 103 insertions(+), 3 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py index 567ca265..e10ff76d 100755 --- a/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py @@ -8,6 +8,8 @@ from cliente_email import fetch_and_process_email from cliente_postgres import ClientPostgresDB from postgres_helpers import get_postgres_conn +import pandas as pd +import io # Configurações básicas da DAG default_args = { @@ -97,10 +99,29 @@ def insert_data_to_db(**context: Dict[str, Any]) -> None: logging.warning("Nenhum dado para inserir no banco.") return + df = pd.read_csv(io.StringIO(csv_data)) + df = df[df["ne_ccor_ano_emissao"].astype(str).str.startswith("20")] + data = df.to_dict(orient="records") + postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) - db.insert_csv_data(csv_data, "empenhos_tesouro", schema="siafi") + unique_key = [ + "ne_ccor", + "natureza_despesa", + "doc_observacao", + "ne_ccor_ano_emissao", + "emissao_dia", + "emissao_mes", + ] + + db.insert_data( + data, + "empenhos_tesouro", + conflict_fields=unique_key, + primary_key=unique_key, + schema="siafi", + ) logging.info("Dados inseridos com sucesso no banco de dados.") except Exception as e: logging.error("Erro ao inserir dados no banco: %s", str(e)) diff --git a/airflow_lappis/dags/data_ingest/pf_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/pf_tesouro_ingest_dag.py index e757437c..3b86153c 100644 --- a/airflow_lappis/dags/data_ingest/pf_tesouro_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/pf_tesouro_ingest_dag.py @@ -5,6 +5,8 @@ from datetime import datetime, timedelta import logging import json +import pandas as pd +import io from cliente_email import fetch_and_process_email from cliente_postgres import ClientPostgresDB from postgres_helpers import get_postgres_conn @@ -179,15 +181,31 @@ def insert_data_to_db(**context: Dict[str, Any]) -> None: logging.warning("Nenhum dado para inserir no banco.") return + df = pd.read_csv(io.StringIO(combined_data)) + data = df.to_dict(orient="records") + postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) - db.insert_csv_data(combined_data, "pf_tesouro", schema="siafi") + db.insert_data(data, "pf_tesouro", schema="siafi") logging.info("Dados inseridos com sucesso no banco de dados.") except Exception as e: logging.error("Erro ao inserir dados no banco: %s", str(e)) raise + def clean_duplicates(**context: Dict[str, Any]) -> None: + """ + Task para remover duplicados da tabela 'siafi.pf_tesouro'. + """ + try: + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + db.remove_duplicates("pf_tesouro", COLUMN_MAPPING, schema="siafi") + + except Exception as e: + logging.error(f"Erro ao executar a limpeza de duplicados: {str(e)}") + raise + # Tarefa 1: Processar os e-mails de programações enviadas/devolvidas process_emails_enviadas_task = PythonOperator( task_id="process_emails_enviadas", @@ -209,16 +227,24 @@ def insert_data_to_db(**context: Dict[str, Any]) -> None: provide_context=True, ) - # Tarefa 4: Inserir os dados no banco de dados + # Tarefa 4: Inserir os dados no db insert_to_db_task = PythonOperator( task_id="insert_to_db", python_callable=insert_data_to_db, provide_context=True, ) + # Tarefa 5: Limpar duplicados no banco de dados + clean_duplicates_task = PythonOperator( + task_id="clean_duplicates", + python_callable=clean_duplicates, + provide_context=True, + ) + # Fluxo da DAG ( [process_emails_enviadas_task, process_emails_recebidas_task] >> combine_data_task >> insert_to_db_task + >> clean_duplicates_task ) diff --git a/airflow_lappis/plugins/cliente_postgres.py b/airflow_lappis/plugins/cliente_postgres.py index f7279063..9da79a47 100755 --- a/airflow_lappis/plugins/cliente_postgres.py +++ b/airflow_lappis/plugins/cliente_postgres.py @@ -324,3 +324,56 @@ def get_nota_credito(self) -> List[Tuple[Any, ...]]: cursor.execute(query) nota_credito = cursor.fetchall() return nota_credito + + def remove_duplicates( + self, table_name: str, column_mapping: Dict[int, str], schema: str = "siafi" + ) -> None: + """ + Remove duplicados de uma tabela e otimiza a tabela. + + Args: + table_name (str): Nome da tabela no banco de dados. + column_mapping (Dict[int, str]): Mapeamento das colunas. + schema (str): Schema do banco de dados (padrão: "siafi"). + """ + try: + columns = ", ".join(column_mapping.values()) + delete_query = f""" + DELETE FROM {schema}.{table_name} + WHERE ctid NOT IN ( + SELECT MIN(ctid) + FROM {schema}.{table_name} + GROUP BY {columns} + ); + """ + vacuum_query = f"VACUUM {schema}.{table_name};" + + logging.info( + f"Executando query para remover duplicados em {schema}.{table_name}" + ) + + with psycopg2.connect(self.conn_str) as conn: + with conn.cursor() as cursor: + cursor.execute(delete_query) + conn.commit() + logging.info( + f"Duplicados removidos com sucesso de {schema}.{table_name}" + ) + + conn = psycopg2.connect(self.conn_str) + conn.autocommit = True + cursor = conn.cursor() + try: + cursor.execute(vacuum_query) + logging.info( + f"VACUUM FULL executado com sucesso em {schema}.{table_name}" + ) + finally: + cursor.close() + conn.close() + + except Exception as e: + logging.error( + f"Erro ao remover duplicados ou otimizar {schema}.{table_name}: {str(e)}" + ) + raise From 02d71680b72ea767178244ed82bda37ee8547115 Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Mon, 21 Apr 2025 02:56:03 +0000 Subject: [PATCH 056/317] Feat/ingestao nc tesouro --- .../dags/data_ingest/nc_tesouro_ingest.dag.py | 244 ++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/nc_tesouro_ingest.dag.py diff --git a/airflow_lappis/dags/data_ingest/nc_tesouro_ingest.dag.py b/airflow_lappis/dags/data_ingest/nc_tesouro_ingest.dag.py new file mode 100644 index 00000000..108822c7 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/nc_tesouro_ingest.dag.py @@ -0,0 +1,244 @@ +from typing import Dict, Any, Optional, List, cast +from airflow import DAG +from airflow.operators.python import PythonOperator +from airflow.models import Variable +from datetime import datetime, timedelta +import logging +import json +import pandas as pd +import io +from cliente_email import fetch_and_process_email +from cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn + +# Configurações básicas da DAG +default_args = { + "owner": "Davi", + "depends_on_past": False, + "retries": 1, + "retry_delay": timedelta(minutes=5), +} + +# Mapeamento das colunas para as notas de crédito +COLUMN_MAPPING = { + 0: "emissao_mes", + 1: "emissao_dia", + 2: "nc", + 3: "nc_transferencia", + 4: "nc_fonte_recursos", + 5: "nc_fonte_recursos_descricao", + 6: "ptres", + 7: "nc_evento", + 8: "nc_evento_descricao", + 9: "ug_responsavel", + 10: "ug_responsavel_descricao", + 11: "natureza_despesa", + 12: "natureza_despesa_detalhada", + 13: "plano_interno", + 14: "plano_detalhado_descricao1", + 15: "plano_detalhado_descricao2", + 16: "favorecido_doc", + 17: "favorecido_doc_descricao", + 18: "nc_valor_linha", + 19: "movimento_liquido", +} + +# Assuntos dos emails a serem processados +EMAIL_SUBJECT_ENVIADAS = "notas_credito_enviadas_devolvidas_a_partir_de_2024" +EMAIL_SUBJECT_RECEBIDAS = "notas_credito_recebidas_a_partir_de_2024" + +# Configurações da DAG +with DAG( + dag_id="email_notas_credito_ingest", + default_args=default_args, + description="Processa anexos das NCs vindo de dois emails, formata e insere no db", + schedule_interval="0 13 * * 1-6", + start_date=datetime(2023, 12, 1), + catchup=False, +) as dag: + + def process_email_data_enviadas(**context: Dict[str, Any]) -> Optional[List[Dict]]: + """ + Função para processar os emails com notas de crédito enviadas. + """ + creds = json.loads(Variable.get("email_credentials")) + + EMAIL = creds["email"] + PASSWORD = creds["password"] + IMAP_SERVER = creds["imap_server"] + SENDER_EMAIL = creds["sender_email"] + + try: + logging.info("Iniciando o processamento das NCs enviadas/devolvidas") + csv_data = cast( + Optional[List[Dict[Any, Any]]], + fetch_and_process_email( + IMAP_SERVER, + EMAIL, + PASSWORD, + SENDER_EMAIL, + EMAIL_SUBJECT_ENVIADAS, + COLUMN_MAPPING, + skiprows=7, + ), + ) + if not csv_data: + logging.warning("Nenhum e-mail encontrado com o assunto de NCs enviadas") + return [] + + logging.info( + "CSV de NCs enviadas processado com sucesso. Dados encontrados: %s", + len(csv_data), + ) + return csv_data + except Exception as e: + logging.error( + "Erro no processamento dos emails de notas de crédito enviadas: %s", + str(e), + ) + raise + + def process_email_data_recebidas(**context: Dict[str, Any]) -> Optional[List[Dict]]: + """ + Função para processar os emails com notas de crédito recebidas. + """ + creds = json.loads(Variable.get("email_credentials")) + + EMAIL = creds["email"] + PASSWORD = creds["password"] + IMAP_SERVER = creds["imap_server"] + SENDER_EMAIL = creds["sender_email"] + + try: + logging.info("Iniciando o processamento das NCs recebidas...") + csv_data = cast( + Optional[List[Dict[Any, Any]]], + fetch_and_process_email( + IMAP_SERVER, + EMAIL, + PASSWORD, + SENDER_EMAIL, + EMAIL_SUBJECT_RECEBIDAS, + column_mapping=None, + skiprows=3, + ), + ) + if not csv_data: + logging.warning( + "Nenhum e-mail encontrado com o assunto de NCs recebidas." + ) + return [] + + logging.info( + "CSV de NCs recebidas processado com sucesso. Dados encontrados: %s", + len(csv_data), + ) + return csv_data + except Exception as e: + logging.error( + "Erro no processamento dos emails de notas de crédito recebidas: %s", + str(e), + ) + raise + + def combine_data(**context: Dict[str, Any]) -> List[Dict]: + """ + Função para combinar os dados dos dois emails. + """ + try: + task_instance: Any = context["ti"] + enviadas_data = ( + task_instance.xcom_pull(task_ids="process_emails_enviadas") or [] + ) + recebidas_data = ( + task_instance.xcom_pull(task_ids="process_emails_recebidas") or [] + ) + + combined_data = enviadas_data + recebidas_data + + logging.info(f"Dados combinados: {len(combined_data)} registros no total.") + return combined_data + except Exception as e: + logging.error(f"Erro ao combinar os dados: {str(e)}") + raise + + def insert_data_to_db(**context: Dict[str, Any]) -> None: + """ + Função para inserir os dados no banco de dados. + Os dados combinados são recuperados do XCom. + """ + try: + task_instance: Any = context["ti"] + combined_data = task_instance.xcom_pull(task_ids="combine_data") + + if not combined_data: + logging.warning("Nenhum dado para inserir no banco.") + return + + df = pd.read_csv(io.StringIO(combined_data)) + data = df.to_dict(orient="records") + + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + db.insert_data(data, "nc_tesouro", schema="siafi") + logging.info("Dados inseridos com sucesso no banco de dados.") + except Exception as e: + logging.error("Erro ao inserir dados no banco: %s", str(e)) + raise + + def clean_duplicates(**context: Dict[str, Any]) -> None: + """ + Task para remover duplicados da tabela 'siafi.pf_tesouro'. + """ + try: + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + db.remove_duplicates("nc_tesouro", COLUMN_MAPPING, schema="siafi") + + except Exception as e: + logging.error(f"Erro ao executar a limpeza de duplicados: {str(e)}") + raise + + # Tarefa 1: Processar os e-mails de notas de crédito enviadas/devolvidas + process_emails_enviadas_task = PythonOperator( + task_id="process_emails_enviadas", + python_callable=process_email_data_enviadas, + provide_context=True, + ) + + # Tarefa 2: Processar os e-mails de notas de crédito recebidas + process_emails_recebidas_task = PythonOperator( + task_id="process_emails_recebidas", + python_callable=process_email_data_recebidas, + provide_context=True, + ) + + # Tarefa 3: Combinar os dados dos dois emails + combine_data_task = PythonOperator( + task_id="combine_data", + python_callable=combine_data, + provide_context=True, + ) + + # Tarefa 4: Inserir os dados no banco de dados + insert_to_db_task = PythonOperator( + task_id="insert_to_db", + python_callable=insert_data_to_db, + provide_context=True, + ) + + # Tarefa 5: Limpar duplicados no banco de dados + clean_duplicates_task = PythonOperator( + task_id="clean_duplicates", + python_callable=clean_duplicates, + provide_context=True, + ) + + # Fluxo da DAG + ( + [process_emails_enviadas_task, process_emails_recebidas_task] + >> combine_data_task + >> insert_to_db_task + >> clean_duplicates_task + ) From 9f6c5126008d21ee4518779a7b72b5ca032e9144 Mon Sep 17 00:00:00 2001 From: VictorSzk Date: Tue, 22 Apr 2025 15:24:26 -0300 Subject: [PATCH 057/317] Consertando config --- airflow_lappis/dags/dbt/ipea/descriptions.yml | 61 +++++++++++++++++++ .../models/contratos_dbt/bronze/contratos.sql | 3 +- .../contratos_dbt/bronze/cronogramas.sql | 3 +- .../models/contratos_dbt/bronze/empenhos.sql | 3 +- .../models/contratos_dbt/bronze/faturas.sql | 3 +- .../dbt/ipea/snapshots/tables_snapshot.yml | 22 +++---- 6 files changed, 74 insertions(+), 21 deletions(-) create mode 100644 airflow_lappis/dags/dbt/ipea/descriptions.yml diff --git a/airflow_lappis/dags/dbt/ipea/descriptions.yml b/airflow_lappis/dags/dbt/ipea/descriptions.yml new file mode 100644 index 00000000..b1054f6d --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/descriptions.yml @@ -0,0 +1,61 @@ +version: 2 + +models: + contratos_dbt: + bronze: + - name: contratos + description: > + Tabela com informações sobre contratos, incluindo detalhes como o valor do contrato, a data de início e término, e o status do contrato. + Esta tabela é fundamental para entender a execução e o cumprimento dos contratos firmados. + A tabela é atualizada diariamente e contém dados de contratos firmados pelo IPEA. + + - name: cronogramas + description: > + Essa tabela contém informações sobre as despesas mensais programadas cada contrato. + + - name: faturas + description: > + Essa tabela contém informações sobre as faturas emitidas mensalmente de cada contrato. + + - name: empenhos + description: > + Essa tabela contém informações sobre os empenhos de cada contrato. + + - name: empenhos_tesouro + description: > + Essa tabela contém informações sobre os empenhos extraídos do SIAFI. + Essa tabela é atualizada diariamente. + + - name: estagios + description: > + Essa tabela contém informações sobre os evento de cada empenho discriminados por mês. + + silver: + - name: contratos_empenhos + description: > + Essa tabela contém informações sobre os empenhos de cada contrato, incluindo detalhes como o valor do empenho, a data de emissão e o status do empenho. + Esta tabela é fundamental para entender a execução + + pessoas_dbt: + + + ted_dbt: + - name: pf_tesouro + description: > + Dados das programações financeiras extraídas do SIAFI, com informações sobre o tipo de programação, a data de execução e o valor. + Essa tabela é atualizada diariamente. + + - name: empenhos_plano_acao + description: > + Dados dos empenhos extraídos do SIAFI, incluído o identificador do plano de ação. + Essa tabela é atualizada diariamente. + + - name: nc_plano_acao + description: > + Dados das notas de crédito extraídas do SIAFI, incluso o identificador do plano de ação. + Essa tabela é atualizada diariamente. + + - name: pf_plano_acao + description: > + Dados das programações financeiras extraídas do SIAFI, includo o identificador do plano de ação. + Essa tabela é atualizada diariamente. \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/contratos.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/contratos.sql index dd290759..6046b96f 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/contratos.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/contratos.sql @@ -114,8 +114,7 @@ with and cast(vigencia_fim as text) ~ '^\d{4}-\d{2}-\d{2}$' -- Retorna NULL se não for uma data válida then to_date(cast(vigencia_fim as text), 'YYYY-MM-DD') - end as vigencia_fim, - now() as updated_at + end as vigencia_fim from {{ source("compras_gov", "contratos") }} ) -- diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/cronogramas.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/cronogramas.sql index cd784c88..4527e8f3 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/cronogramas.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/cronogramas.sql @@ -13,8 +13,7 @@ with anoref::integer as anoref, retroativo::text as retroativo, replace(replace(valor::text, '.', ''), ',', '.')::numeric(15, 2) as valor, - vencimento::date as vencimento, - now() as updated_at + vencimento::date as vencimento from {{ source("compras_gov", "cronograma") }} ), diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos.sql index fcd4c01b..fa5742b7 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos.sql @@ -49,8 +49,7 @@ with and data_emissao::text ~ '^\d{4}-\d{2}-\d{2}$' -- Retorna NULL se não for uma data válida then to_date(data_emissao::text, 'YYYY-MM-DD') - end as data_emissao, - now() as updated_at + end as data_emissao from {{ source("compras_gov", "empenhos") }} ) diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/faturas.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/faturas.sql index 3889b68d..8c7c3cea 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/faturas.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/faturas.sql @@ -43,8 +43,7 @@ with f.dados_empenho ->> 'id_empenho' as id_empenho, upper(f.dados_empenho ->> 'numero_empenho') as numero_empenho, f.dados_empenho ->> 'valor_empenho' as valor_empenho, - f.dados_empenho ->> 'subelemento' as subelemento, - now() as updated_at + f.dados_empenho ->> 'subelemento' as subelemento from faturas_raw as f ) -- select * diff --git a/airflow_lappis/dags/dbt/ipea/snapshots/tables_snapshot.yml b/airflow_lappis/dags/dbt/ipea/snapshots/tables_snapshot.yml index 7c8b5400..84d36234 100644 --- a/airflow_lappis/dags/dbt/ipea/snapshots/tables_snapshot.yml +++ b/airflow_lappis/dags/dbt/ipea/snapshots/tables_snapshot.yml @@ -5,9 +5,8 @@ snapshots: schema: snapshots database: analytics unique_key: id - strategy: timestamp - updated_at: updated_at - dbt_valid_to_current: "to_timestamp('9999-12-31', 'YYYY-MM-DD')" + strategy: check + check_cols: [situacao, num_parcelas, valor_parcela, valor_global, valor_acumulado] - name: faturas_snapshot relation: ref('faturas') @@ -15,19 +14,17 @@ snapshots: schema: snapshots database: analytics unique_key: [id, id_empenho] - strategy: timestamp - updated_at: updated_at - dbt_valid_to_current: "to_timestamp('9999-12-31', 'YYYY-MM-DD')" + strategy: check + check_cols: [situacao, valor, juros, multa, glosa] - name: empenhos_snapshot relation: ref('empenhos') config: schema: snapshots database: analytics - unique_key: id - strategy: timestamp - updated_at: updated_at - dbt_valid_to_current: "to_timestamp('9999-12-31', 'YYYY-MM-DD')" + unique_key: [id, contrato_id] + strategy: check + check_cols: [empenhado, aliquidar, liquidado, pago, rpinscrito, rpaliquidar, rpliquidado, rppago] - name: cronogramas_snapshot relation: ref('cronogramas') @@ -35,6 +32,5 @@ snapshots: schema: snapshots database: analytics unique_key: id - strategy: timestamp - updated_at: updated_at - dbt_valid_to_current: "to_timestamp('9999-12-31', 'YYYY-MM-DD')" \ No newline at end of file + strategy: check + check_cols: [valor, retroativo, observacao] \ No newline at end of file From cdfdb7d29b2488021ce5f6dd5a4f47706e23fbf3 Mon Sep 17 00:00:00 2001 From: VictorSzk Date: Wed, 23 Apr 2025 15:01:11 -0300 Subject: [PATCH 058/317] introduzindo dbt docs --- .../dags/dbt/ipea/models/schema.yml | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/schema.yml diff --git a/airflow_lappis/dags/dbt/ipea/models/schema.yml b/airflow_lappis/dags/dbt/ipea/models/schema.yml new file mode 100644 index 00000000..cf78cd06 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/schema.yml @@ -0,0 +1,117 @@ +# Este arquivo deve ser usado para descrições dos modelos +# e para configurar testes + +version: 2 + +models: + + # Contratos DBT + + ## Bronze + - name: contratos + description: > + Tabela com informações sobre contratos, incluindo detalhes como o valor do contrato, a data de início e término, e o status do contrato. + Esta tabela é fundamental para entender a execução e o cumprimento dos contratos firmados. + A tabela é atualizada diariamente e contém dados de contratos firmados pelo IPEA. + columns: + - name: id + description: > + Identificador único do contrato, utilizado para referenciar o contrato em outras tabelas e análises. + + - name: cronogramas + description: > + Essa tabela contém informações sobre as despesas mensais programadas cada contrato. + + - name: faturas + description: "Essa tabela contém informações sobre as faturas emitidas mensalmente de cada contrato." + + - name: empenhos + description: > + Essa tabela contém informações sobre os empenhos de cada contrato. + + - name: empenhos_tesouro + description: > + Essa tabela contém informações sobre movimentação financeira dos empenhos extraídos do SIAFI. + + - name: estagios + description: > + Essa tabela contém informações sobre os evento de cada empenho discriminados por mês. + + ## Silver + - name: contratos_empenhos + description: Essa tabela combina as informações de movimentação financeira dos empenhos com os ids de contrato do IPEA. + + - name: contratos_estagios + description: > + Essa tabela combina as informações de movimentação financeira dos empenhos com os ids de contrato do IPEA mensalmente. + + - name: estagios_mensal + description: > + Essa tabela discrimina os valores empenhados, liquidados e pagos mensalmente extraídos do SIAFI para cada contrato. + + - name: cronogramas_fatura_mensal + description: > + Essa tabela discrimina os falores programados e faturados mensalmente pelo ComprasGov para cada contrato. + + ## Golds + - name: contratos_resumo + description: > + Essa tabela contém um resumo dos contratos, informações contratuais como valor global e valor pago, + e situação atual, como em vigência ou pendente de baixa. + + - name: contratos_comparativo_mensal + description: > + Essa tabela contém um comparativo mensal dos contratos, discriminando + os valores empenhados, liquidados e pagos do SIAFI, + programados e faturados do ComprasGov. + + ## View + - name: identificadores + description: > + Essa tabela contém os identificadores dos contratos, empenhos e faturas, + Essa tabela é utilizada para facilitar joins. + + # Pessoas DBT + + # TED DBT + - name: pf_tesouro + description: > + Dados das programações financeiras extraídas do SIAFI, com informações sobre o tipo de programação, a data de execução e o valor. + Essa tabela é atualizada diariamente. + + - name: empenhos_plano_acao + description: > + Dados dos empenhos extraídos do SIAFI, incluído o identificador do plano de ação. + Essa tabela é atualizada diariamente. + + - name: nc_plano_acao + description: > + Dados das notas de crédito extraídas do SIAFI, incluso o identificador do plano de ação. + Essa tabela é atualizada diariamente. + + - name: pf_plano_acao + description: > + Dados das programações financeiras extraídas do SIAFI, includo o identificador do plano de ação. + Essa tabela é atualizada diariamente. + +macros: + - name: create_udfs + description: > + Função que cria as UDFs necessárias para o funcionamento do projeto. + Essa função deve ser chamada no início de cada run para garantir que todas as UDFs estejam disponíveis. + + - name: generate_schema_name + description: > + Função que gera o nome do schema a ser utilizado no projeto. + A função dentro desta macro é built-in do dbt. + + ## UDFS + - name: create_f_parse_dates + description: > + Função que cria a UDF f_parse_dates, que é utilizada para converter texto no formato MÊS(texto)/ANO(numero) em datas. + arguments: + - name: in_text + type: text + description: > + Texto a ser convertido em data. + O texto deve estar no formato MÊS(texto)/ANO(numero). Ex.: FEV/2024 -> 2024-02-01 \ No newline at end of file From 17c02f3a8fb2b6de4ff1d53b6fa425e0f33d9f04 Mon Sep 17 00:00:00 2001 From: Joyce Dionizio Date: Mon, 28 Apr 2025 17:56:39 +0000 Subject: [PATCH 059/317] Feat/siape ingest --- airflow_lappis/plugins/cliente_siape.py | 128 ++++++++++++++---- .../siape/consultaDadosFuncionais.xml.j2 | 14 ++ 2 files changed, 112 insertions(+), 30 deletions(-) create mode 100644 airflow_lappis/templates/siape/consultaDadosFuncionais.xml.j2 diff --git a/airflow_lappis/plugins/cliente_siape.py b/airflow_lappis/plugins/cliente_siape.py index 9d9813d7..9aa82243 100755 --- a/airflow_lappis/plugins/cliente_siape.py +++ b/airflow_lappis/plugins/cliente_siape.py @@ -1,25 +1,34 @@ -from typing import Dict, Any -from requests import Session +from typing import Dict import requests +import xml.etree.ElementTree as ET +from jinja2 import Environment, FileSystemLoader -from zeep import Transport, Client - -class ClienteSiape(object): +class ClienteSiape: + """ + Client to consume the SIAPE SOAP API using OAuth2 authentication + and dynamic XML generation with Jinja2 templates. + """ BEARER_ENDPOINT = ( "https://apigateway.conectagov.estaleiro.serpro.gov.br/oauth2/jwt-token/" ) SOAP_ENDPOINT = "https://apigateway.conectagov.estaleiro.serpro.gov.br/api-consulta-siape/v1/consulta-siape" - def __init__(self, oauth_username: str, oauth_password: str) -> None: - token = ClienteSiape._get_token(oauth_username, oauth_password) - headers = ClienteSiape._get_headers(token) - session = Session() - session.headers.update(headers) - transport = Transport(session=session) - self.base_url = ClienteSiape.SOAP_ENDPOINT - self.client = Client(ClienteSiape.SOAP_ENDPOINT, transport=transport) + def __init__( + self, oauth_username: str, oauth_password: str, cpf_usuario: str + ) -> None: + """ + Initialize the SIAPE client with authentication and request headers. + + Args: + oauth_username (str): OAuth username for token retrieval. + oauth_password (str): OAuth password for token retrieval. + cpf_usuario (str): CPF used in the 'x-cpf-usuario' header for SIAPE requests. + """ + token = self._get_token(oauth_username, oauth_password) + self.headers = self._get_headers(token, cpf_usuario) + self.env = Environment(loader=FileSystemLoader("templates/siape")) @staticmethod def _get_token(oauth_username: str, oauth_password: str) -> str: @@ -27,22 +36,24 @@ def _get_token(oauth_username: str, oauth_password: str) -> str: Gets the token for the client. Args: - oauth_username (str): The OAuth username. - oauth_password (str): The OAuth password. + oauth_username (str): OAuth username. + oauth_password (str): OAuth password. Returns: - str: The token. + str: Access token. """ - auth_response = requests.get( + data = {"grant_type": "client_credentials"} + response = requests.post( ClienteSiape.BEARER_ENDPOINT, auth=(oauth_username, oauth_password), + data=data, headers={"Content-Type": "application/x-www-form-urlencoded"}, ) - auth_json = auth_response.json() - return str(auth_json.get("access_token", "")) + response.raise_for_status() + return str(response.json()["access_token"]) @staticmethod - def _get_headers(token: str) -> Dict[str, str]: + def _get_headers(token: str, cpf_usuario: str) -> Dict[str, str]: """ Builds the headers for the client. @@ -52,20 +63,77 @@ def _get_headers(token: str) -> Dict[str, str]: Returns: Dict[str, str]: The headers. """ - return {"Authorization": f"Bearer {token}"} + return { + "Authorization": f"Bearer {token}", + "x-cpf-usuario": cpf_usuario, + "Content-Type": "application/xml", + } - def method(self, method_name: str) -> Any: + def render_xml(self, template_name: str, context: Dict[str, str]) -> str: """ - Applies the method to the client. + Render XML from a Jinja2 template and context. Args: - method_name (str): The method name. + template_name (str): Template filename + (e.g. 'consultaDadosFuncionais.xml.j2'). + context (Dict[str, str]): Data to inject into the template. Returns: - Any: The response. + str: Rendered XML string. + """ + template = self.env.get_template(template_name) + return template.render(context) + + def enviar_soap(self, xml: str) -> str: + """ + Send the XML payload to the SIAPE SOAP endpoint. + + Args: + xml (str): The complete XML request. + + Returns: + str: The raw XML response. + """ + response = requests.post( + ClienteSiape.SOAP_ENDPOINT, headers=self.headers, data=xml + ) + response.raise_for_status() + return response.text + + def call(self, template_name: str, context: Dict[str, str]) -> str: """ - try: - response = getattr(self.client.service, method_name) - return response - except Exception as e: - raise Exception(f"Error making SOAP request: {str(e)}") + Execute a SOAP request using a Jinja2 template and parameters. + + Args: + template_name (str): Jinja2 template file name. + context (Dict[str, str]): Parameters for rendering the XML. + + Returns: + str: The raw XML response. + """ + xml = self.render_xml(template_name, context) + return self.enviar_soap(xml) + + @staticmethod + def parse_xml_to_dict(xml_string: str) -> Dict[str, str]: + """ + Parse a SOAP XML response and return a dictionary with tag names and values. + + Args: + xml_string (str): SOAP XML response. + + Returns: + Dict[str, str]: Flattened dictionary of XML data. + """ + ns = {"soapenv": "http://schemas.xmlsoap.org/soap/envelope/"} + root = ET.fromstring(xml_string) + body = root.find("soapenv:Body", ns) + if body is None: + return {"error": "Missing SOAP Body"} + + response_elem = list(body)[0] + return { + child.tag.split("}")[-1]: child.text.strip() + for child in response_elem.iter() + if child.text and child.text.strip() + } diff --git a/airflow_lappis/templates/siape/consultaDadosFuncionais.xml.j2 b/airflow_lappis/templates/siape/consultaDadosFuncionais.xml.j2 new file mode 100644 index 00000000..6d66114b --- /dev/null +++ b/airflow_lappis/templates/siape/consultaDadosFuncionais.xml.j2 @@ -0,0 +1,14 @@ + + + + + {{ siglaSistema }} + {{ nomeSistema }} + {{ senha }} + {{ cpf }} + {{ codOrgao }} + {{ parmExistPag }} + {{ parmTipoVinculo }} + + + From 7d63fc574115e84e1cadb10154db292108c1fee5 Mon Sep 17 00:00:00 2001 From: VictorSzk Date: Mon, 28 Apr 2025 16:47:05 -0300 Subject: [PATCH 060/317] Alterando scheduler --- airflow_lappis/dags/dbt/ipea/cosmos_dag.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/airflow_lappis/dags/dbt/ipea/cosmos_dag.py b/airflow_lappis/dags/dbt/ipea/cosmos_dag.py index 4b586210..2e30ded1 100755 --- a/airflow_lappis/dags/dbt/ipea/cosmos_dag.py +++ b/airflow_lappis/dags/dbt/ipea/cosmos_dag.py @@ -19,7 +19,10 @@ execution_config=ExecutionConfig( dbt_executable_path=f"{os.environ['AIRFLOW_REPO_BASE']}/.local/bin/dbt", ), - schedule_interval="@daily", + # Expressãp cron para agendar a execução do DAG diariamente às 01:00 + # Futuralmente isso pode ser substituído por um cronograma mais específico + # com dependências entre os DAGs + schedule_interval=" 0 1 * * *", start_date=datetime(2025, 1, 1), catchup=False, dag_id="ipea_cosmos_dag", From f96d7b9541c740ab09ece0396d262f26402334c5 Mon Sep 17 00:00:00 2001 From: Joyce Date: Mon, 28 Apr 2025 18:24:31 -0300 Subject: [PATCH 061/317] feat(templates): adiciona templates de todas req do siape --- .../siape/consultaDadosAfastamento.xml.j2 | 15 +++++++++++++++ .../consultaDadosAfastamentoHistorico.xml.j2 | 19 +++++++++++++++++++ .../siape/consultaDadosCurriculo.xml.j2 | 15 +++++++++++++++ .../siape/consultaDadosDependentes.xml.j2 | 15 +++++++++++++++ .../siape/consultaDadosEscolares.xml.j2 | 15 +++++++++++++++ .../siape/consultaDadosFinanceiros.xml.j2 | 15 +++++++++++++++ .../templates/siape/consultaDadosPA.xml.j2 | 15 +++++++++++++++ .../siape/consultaDadosPessoais.xml.j2 | 15 +++++++++++++++ .../templates/siape/consultaDadosUorg.xml.j2 | 15 +++++++++++++++ .../listaInformacoesAposentadoria.xml.j2 | 14 ++++++++++++++ ...istaInformacoesAposentadoriaParcial.xml.j2 | 16 ++++++++++++++++ .../templates/siape/listaServidores.xml.j2 | 14 ++++++++++++++ .../templates/siape/listaUorgs.xml.j2 | 14 ++++++++++++++ 13 files changed, 197 insertions(+) create mode 100644 airflow_lappis/templates/siape/consultaDadosAfastamento.xml.j2 create mode 100644 airflow_lappis/templates/siape/consultaDadosAfastamentoHistorico.xml.j2 create mode 100644 airflow_lappis/templates/siape/consultaDadosCurriculo.xml.j2 create mode 100644 airflow_lappis/templates/siape/consultaDadosDependentes.xml.j2 create mode 100644 airflow_lappis/templates/siape/consultaDadosEscolares.xml.j2 create mode 100644 airflow_lappis/templates/siape/consultaDadosFinanceiros.xml.j2 create mode 100644 airflow_lappis/templates/siape/consultaDadosPA.xml.j2 create mode 100644 airflow_lappis/templates/siape/consultaDadosPessoais.xml.j2 create mode 100644 airflow_lappis/templates/siape/consultaDadosUorg.xml.j2 create mode 100644 airflow_lappis/templates/siape/listaInformacoesAposentadoria.xml.j2 create mode 100644 airflow_lappis/templates/siape/listaInformacoesAposentadoriaParcial.xml.j2 create mode 100644 airflow_lappis/templates/siape/listaServidores.xml.j2 create mode 100644 airflow_lappis/templates/siape/listaUorgs.xml.j2 diff --git a/airflow_lappis/templates/siape/consultaDadosAfastamento.xml.j2 b/airflow_lappis/templates/siape/consultaDadosAfastamento.xml.j2 new file mode 100644 index 00000000..03ef212c --- /dev/null +++ b/airflow_lappis/templates/siape/consultaDadosAfastamento.xml.j2 @@ -0,0 +1,15 @@ + + + + + + {{ siglaSistema }} + {{ nomeSistema }} + {{ senha }} + {{ cpf }} + {{ codOrgao }} + {{ parmExistPag }} + {{ parmTipoVinculo }} + + + diff --git a/airflow_lappis/templates/siape/consultaDadosAfastamentoHistorico.xml.j2 b/airflow_lappis/templates/siape/consultaDadosAfastamentoHistorico.xml.j2 new file mode 100644 index 00000000..887b3203 --- /dev/null +++ b/airflow_lappis/templates/siape/consultaDadosAfastamentoHistorico.xml.j2 @@ -0,0 +1,19 @@ + + + + + + {{ siglaSistema }} + {{ nomeSistema }} + {{ senha }} + {{ cpf }} + {{ codOrgao }} + {{ parmExistPag }} + {{ parmTipoVinculo }} + {{ anoInicial }} + {{ mesInicial }} + {{ anoFinal }} + {{ mesFinal }} + + + diff --git a/airflow_lappis/templates/siape/consultaDadosCurriculo.xml.j2 b/airflow_lappis/templates/siape/consultaDadosCurriculo.xml.j2 new file mode 100644 index 00000000..74937137 --- /dev/null +++ b/airflow_lappis/templates/siape/consultaDadosCurriculo.xml.j2 @@ -0,0 +1,15 @@ + + + + + + {{ siglaSistema }} + {{ nomeSistema }} + {{ senha }} + {{ cpf }} + {{ codOrgao }} + {{ parmExistPag }} + {{ parmTipoVinculo }} + + + diff --git a/airflow_lappis/templates/siape/consultaDadosDependentes.xml.j2 b/airflow_lappis/templates/siape/consultaDadosDependentes.xml.j2 new file mode 100644 index 00000000..486ad749 --- /dev/null +++ b/airflow_lappis/templates/siape/consultaDadosDependentes.xml.j2 @@ -0,0 +1,15 @@ + + + + + + {{ siglaSistema }} + {{ nomeSistema }} + {{ senha }} + {{ cpf }} + {{ codOrgao }} + {{ parmExistPag }} + {{ parmTipoVinculo }} + + + diff --git a/airflow_lappis/templates/siape/consultaDadosEscolares.xml.j2 b/airflow_lappis/templates/siape/consultaDadosEscolares.xml.j2 new file mode 100644 index 00000000..6b4409cd --- /dev/null +++ b/airflow_lappis/templates/siape/consultaDadosEscolares.xml.j2 @@ -0,0 +1,15 @@ + + + + + + {{ siglaSistema }} + {{ nomeSistema }} + {{ senha }} + {{ cpf }} + {{ codOrgao }} + {{ parmExistPag }} + {{ parmTipoVinculo }} + + + diff --git a/airflow_lappis/templates/siape/consultaDadosFinanceiros.xml.j2 b/airflow_lappis/templates/siape/consultaDadosFinanceiros.xml.j2 new file mode 100644 index 00000000..39d3ee25 --- /dev/null +++ b/airflow_lappis/templates/siape/consultaDadosFinanceiros.xml.j2 @@ -0,0 +1,15 @@ + + + + + + {{ siglaSistema }} + {{ nomeSistema }} + {{ senha }} + {{ cpf }} + {{ codOrgao }} + {{ parmExistPag }} + {{ parmTipoVinculo }} + + + diff --git a/airflow_lappis/templates/siape/consultaDadosPA.xml.j2 b/airflow_lappis/templates/siape/consultaDadosPA.xml.j2 new file mode 100644 index 00000000..9c8e5983 --- /dev/null +++ b/airflow_lappis/templates/siape/consultaDadosPA.xml.j2 @@ -0,0 +1,15 @@ + + + + + + {{ siglaSistema }} + {{ nomeSistema }} + {{ senha }} + {{ cpf }} + {{ codOrgao }} + {{ parmExistPag }} + {{ parmTipoVinculo }} + + + diff --git a/airflow_lappis/templates/siape/consultaDadosPessoais.xml.j2 b/airflow_lappis/templates/siape/consultaDadosPessoais.xml.j2 new file mode 100644 index 00000000..d0e8c0e5 --- /dev/null +++ b/airflow_lappis/templates/siape/consultaDadosPessoais.xml.j2 @@ -0,0 +1,15 @@ + + + + + + {{ siglaSistema }} + {{ nomeSistema }} + {{ senha }} + {{ cpf }} + {{ codOrgao }} + {{ parmExistPag }} + {{ parmTipoVinculo }} + + + diff --git a/airflow_lappis/templates/siape/consultaDadosUorg.xml.j2 b/airflow_lappis/templates/siape/consultaDadosUorg.xml.j2 new file mode 100644 index 00000000..a9854c08 --- /dev/null +++ b/airflow_lappis/templates/siape/consultaDadosUorg.xml.j2 @@ -0,0 +1,15 @@ + + + + + + {{ siglaSistema }} + {{ nomeSistema }} + {{ senha }} + {{ cpf }} + {{ codOrgao }} + {{ parmExistPag }} + {{ parmTipoVinculo }} + + + diff --git a/airflow_lappis/templates/siape/listaInformacoesAposentadoria.xml.j2 b/airflow_lappis/templates/siape/listaInformacoesAposentadoria.xml.j2 new file mode 100644 index 00000000..720dfbd1 --- /dev/null +++ b/airflow_lappis/templates/siape/listaInformacoesAposentadoria.xml.j2 @@ -0,0 +1,14 @@ + + + + + + {{ siglaSistema }} + {{ nomeSistema }} + {{ senha }} + {{ cpf }} + {{ orgao }} + {{ matricula }} + + + diff --git a/airflow_lappis/templates/siape/listaInformacoesAposentadoriaParcial.xml.j2 b/airflow_lappis/templates/siape/listaInformacoesAposentadoriaParcial.xml.j2 new file mode 100644 index 00000000..e452685a --- /dev/null +++ b/airflow_lappis/templates/siape/listaInformacoesAposentadoriaParcial.xml.j2 @@ -0,0 +1,16 @@ + + + + + + {{ siglaSistema }} + {{ nomeSistema }} + {{ senha }} + {{ cpf }} + {{ anoMes }} + {{ codOrgao }} + {{ nivelSituacaoFuncional }} + {{ matricula }} + + + diff --git a/airflow_lappis/templates/siape/listaServidores.xml.j2 b/airflow_lappis/templates/siape/listaServidores.xml.j2 new file mode 100644 index 00000000..0388e724 --- /dev/null +++ b/airflow_lappis/templates/siape/listaServidores.xml.j2 @@ -0,0 +1,14 @@ + + + + + + {{ siglaSistema }} + {{ nomeSistema }} + {{ senha }} + {{ cpf }} + {{ codOrgao }} + {{ codUorg }} + + + diff --git a/airflow_lappis/templates/siape/listaUorgs.xml.j2 b/airflow_lappis/templates/siape/listaUorgs.xml.j2 new file mode 100644 index 00000000..57cf959a --- /dev/null +++ b/airflow_lappis/templates/siape/listaUorgs.xml.j2 @@ -0,0 +1,14 @@ + + + + + + {{ siglaSistema }} + {{ nomeSistema }} + {{ senha }} + {{ cpf }} + {{ codOrgao }} + {{ codUorg }} + + + From 78fb2366d1d017acdaf0e19b9589535dd0e9999e Mon Sep 17 00:00:00 2001 From: Joyce Dionizio Date: Tue, 29 Apr 2025 13:33:39 +0000 Subject: [PATCH 062/317] feat(siape): ajusta variaveis do ambiente --- .../dags/data_ingest/dados_funcionais_dag.py | 72 +++++++++++++++++++ airflow_lappis/plugins/cliente_siape.py | 32 +++++---- 2 files changed, 90 insertions(+), 14 deletions(-) create mode 100644 airflow_lappis/dags/data_ingest/dados_funcionais_dag.py diff --git a/airflow_lappis/dags/data_ingest/dados_funcionais_dag.py b/airflow_lappis/dags/data_ingest/dados_funcionais_dag.py new file mode 100644 index 00000000..4435ebd3 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/dados_funcionais_dag.py @@ -0,0 +1,72 @@ +import os +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from postgres_helpers import get_postgres_conn +from cliente_siape import ClienteSiape +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Joyce", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["siape", "dados_funcionais"], +) +def siape_dados_funcionais_dag() -> None: + """ + DAG que extrai dados funcionais do SIAPE via API SOAP + e insere no schema 'siape', tabela 'dados_funcionais'. + """ + + @task + def fetch_and_store_dados_funcionais() -> None: + logging.info( + "[siape_dados_funcionais_dag] Iniciando extração dos dados funcionais" + ) + + cliente_siape = ClienteSiape() + + context = { + "siglaSistema": "PETRVS-IPEA", + "nomeSistema": "PDG-PETRVS-IPEA", + "senha": os.getenv("SIAPE_PASSWORD_USER"), + "cpf": os.getenv("SIAPE_CPF_USER"), + "codOrgao": "45206", + "parmExistPag": "b", + "parmTipoVinculo": "c", + } + + resposta_xml = cliente_siape.call("consultaDadosFuncionais.xml.j2", context) + dados_dict = ClienteSiape.parse_xml_to_dict(resposta_xml) + + if not dados_dict: + logging.warning( + "[siape_dados_funcionais_dag] Nenhum dado retornado da API SIAPE" + ) + return + + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + logging.info("[siape_dados_funcionais_dag] Inserindo dados no banco de dados") + + db.insert_data( + [dados_dict], + table_name="dados_funcionais", + conflict_fields=None, + primary_key=None, + schema="siape", + ) + + logging.info("[siape_dados_funcionais_dag] Dados inseridos com sucesso") + + fetch_and_store_dados_funcionais() + + +dag_instance = siape_dados_funcionais_dag() diff --git a/airflow_lappis/plugins/cliente_siape.py b/airflow_lappis/plugins/cliente_siape.py index 9aa82243..0b41995d 100755 --- a/airflow_lappis/plugins/cliente_siape.py +++ b/airflow_lappis/plugins/cliente_siape.py @@ -1,4 +1,5 @@ -from typing import Dict +import os +from typing import Dict, Any import requests import xml.etree.ElementTree as ET from jinja2 import Environment, FileSystemLoader @@ -15,19 +16,22 @@ class ClienteSiape: ) SOAP_ENDPOINT = "https://apigateway.conectagov.estaleiro.serpro.gov.br/api-consulta-siape/v1/consulta-siape" - def __init__( - self, oauth_username: str, oauth_password: str, cpf_usuario: str - ) -> None: + def __init__(self) -> None: """ - Initialize the SIAPE client with authentication and request headers. - - Args: - oauth_username (str): OAuth username for token retrieval. - oauth_password (str): OAuth password for token retrieval. - cpf_usuario (str): CPF used in the 'x-cpf-usuario' header for SIAPE requests. + Initialize the SIAPE client using environment variables: + - SIAPE_BEARER_USER + - SIAPE_BEARER_PASSWORD + - SIAPE_CPF_USER """ - token = self._get_token(oauth_username, oauth_password) - self.headers = self._get_headers(token, cpf_usuario) + self.oauth_user = os.getenv("SIAPE_BEARER_USER") + self.oauth_password = os.getenv("SIAPE_BEARER_PASSWORD") + self.cpf_usuario = os.getenv("SIAPE_CPF_USER") + + if not all([self.oauth_user, self.oauth_password, self.cpf_usuario]): + raise ValueError("Variáveis de ambiente do SIAPE estão incompletas") + + token = self._get_token(self.oauth_user, self.oauth_password) + self.headers = self._get_headers(token, self.cpf_usuario) self.env = Environment(loader=FileSystemLoader("templates/siape")) @staticmethod @@ -49,8 +53,8 @@ def _get_token(oauth_username: str, oauth_password: str) -> str: data=data, headers={"Content-Type": "application/x-www-form-urlencoded"}, ) - response.raise_for_status() - return str(response.json()["access_token"]) + json_response: dict[str, Any] = response.json() + return str(json_response["access_token"]) @staticmethod def _get_headers(token: str, cpf_usuario: str) -> Dict[str, str]: From 8bacd7047aeaae2a2bbe483241bd1a444f4c6656 Mon Sep 17 00:00:00 2001 From: Mateus de Castro Date: Tue, 29 Apr 2025 16:11:15 +0000 Subject: [PATCH 063/317] Fix dash faturas --- .../silver/contratos_faturas.sql | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_faturas.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_faturas.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_faturas.sql new file mode 100644 index 00000000..19f7ea7b --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_faturas.sql @@ -0,0 +1,53 @@ +{{ config(materialized="table") }} + +with + contratos as ( + select + id::int as contrato_id, + fornecedor_cnpj_cpf_idgener, + processo as processo_contrato, + numero as numero_contrato, + objeto as objeto_contrato + from {{ ref("bronze", "contratos") }} + ), + + faturas_base as (select * from {{ ref("bronze", "faturas") }}) + +select + f.id, + f.contrato_id, + c.numero_contrato, + c.processo_contrato as contrato_processo, + c.fornecedor_cnpj_cpf_idgener, + c.objeto_contrato, + f.tipolistafatura_id, + f.justificativafatura_id, + f.sfadrao_id, + f.numero, + f.emissao, + f.prazo, + f.vencimento, + f.valor, + f.juros, + f.multa, + f.glosa, + f.valorliquido, + f.processo, + f.protocolo, + f.ateste, + f.repactuacao, + f.infcomplementar, + f.mesref, + f.anoref, + f.situacao, + f.chave_nfe, + f.dados_referencia, + f.dados_item_faturado, + f.dados_empenho, + f.id_empenho, + f.numero_empenho, + f.valor_empenho, + f.subelemento, + f.updated_at +from faturas_base f +left join contratos c on f.contrato_id = c.contrato_id From 5b791157f3e79aba817f0930ee8c82e0dea0a667 Mon Sep 17 00:00:00 2001 From: mat054 Date: Wed, 30 Apr 2025 12:50:34 -0300 Subject: [PATCH 064/317] fix dbt contratos_faturas --- .../ipea/models/contratos_dbt/silver/contratos_faturas.sql | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_faturas.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_faturas.sql index 19f7ea7b..ba4327f8 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_faturas.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_faturas.sql @@ -8,10 +8,10 @@ with processo as processo_contrato, numero as numero_contrato, objeto as objeto_contrato - from {{ ref("bronze", "contratos") }} + from {{ ref("contratos") }} ), - faturas_base as (select * from {{ ref("bronze", "faturas") }}) + faturas_base as (select * from {{ ref("faturas") }}) select f.id, @@ -47,7 +47,6 @@ select f.id_empenho, f.numero_empenho, f.valor_empenho, - f.subelemento, - f.updated_at + f.subelemento from faturas_base f left join contratos c on f.contrato_id = c.contrato_id From 2262189e0a8083867de4636988aefc45a72a88ef Mon Sep 17 00:00:00 2001 From: Joyce Dionizio Date: Mon, 5 May 2025 17:20:29 +0000 Subject: [PATCH 065/317] Fix/cliente siape --- airflow_lappis/plugins/cliente_siape.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/airflow_lappis/plugins/cliente_siape.py b/airflow_lappis/plugins/cliente_siape.py index 0b41995d..2d1c3630 100755 --- a/airflow_lappis/plugins/cliente_siape.py +++ b/airflow_lappis/plugins/cliente_siape.py @@ -32,7 +32,9 @@ def __init__(self) -> None: token = self._get_token(self.oauth_user, self.oauth_password) self.headers = self._get_headers(token, self.cpf_usuario) - self.env = Environment(loader=FileSystemLoader("templates/siape")) + base_path = os.environ["AIRFLOW_REPO_BASE"] + templates_path = f"{base_path}/templates/siape" + self.env = Environment(loader=FileSystemLoader(templates_path)) @staticmethod def _get_token(oauth_username: str, oauth_password: str) -> str: From 55a9d4df7e801b58214dc7073574f54293b23686 Mon Sep 17 00:00:00 2001 From: "pedro.rossi1" Date: Mon, 5 May 2025 18:44:52 -0300 Subject: [PATCH 066/317] fix: compose: adicionando env local de exemplo --- local.env | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 local.env diff --git a/local.env b/local.env new file mode 100644 index 00000000..d1da9b84 --- /dev/null +++ b/local.env @@ -0,0 +1,28 @@ +# <---------- Airflow ----------> + +AIRFLOW_IMAGE_NAME=apache/airflow:latest +AIRFLOW__CORE__FERNET_KEY='lmnHJcz5u4D8SPEeD4qwAf1TUk_yLXXnQfwlvQ8MXsU=' +AIRFLOW_HOME=/opt/airflow/ +_AIRFLOW_WWW_USER_USERNAME=airflow +_AIRFLOW_WWW_USER_PASSWORD=airflow +AIRFLOW_UID=50000 + + +# <---------- Postgres Airflow ----------> + +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres +POSTGRES_DB=postgres + + +# <---------- Postgres Data Warehouse ----------> +POSTGRES_USER_DW=postgres_dw +POSTGRES_PASSWORD_DW=postgres_dw +POSTGRES_DB_DW=data_warehouse + + +# <---------- MinIO ----------> +MINIO_ENDPOINT=minio:9000 +MINIO_ACCESS_KEY=minioadmin +MINIO_SECRET_KEY=minioadmin +MINIO_BUCKET=data-lake-ipea From adce4daffe2d1a6a5a26ef5630778441e1b088b7 Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Mon, 5 May 2025 21:49:25 +0000 Subject: [PATCH 067/317] Feat/test dbt --- .../macros/data_quality/row_count_match.sql | 14 + .../data_quality/verificacao_tipagem.sql | 25 ++ .../dags/dbt/ipea/models/schema.yml | 254 +++++++++++++++++- 3 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 airflow_lappis/dags/dbt/ipea/macros/data_quality/row_count_match.sql create mode 100644 airflow_lappis/dags/dbt/ipea/macros/data_quality/verificacao_tipagem.sql diff --git a/airflow_lappis/dags/dbt/ipea/macros/data_quality/row_count_match.sql b/airflow_lappis/dags/dbt/ipea/macros/data_quality/row_count_match.sql new file mode 100644 index 00000000..f248e30c --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/macros/data_quality/row_count_match.sql @@ -0,0 +1,14 @@ +{% macro test_row_count_match(model, source_table, target_table) %} + with + source_count as (select count(*) as row_count from {{ source_table }}), + target_count as (select count(*) as row_count from {{ target_table }}), + comparison as ( + select + source_count.row_count as source_row_count, + target_count.row_count as target_row_count + from source_count, target_count + ) + select * + from comparison + where source_row_count != target_row_count +{% endmacro %} diff --git a/airflow_lappis/dags/dbt/ipea/macros/data_quality/verificacao_tipagem.sql b/airflow_lappis/dags/dbt/ipea/macros/data_quality/verificacao_tipagem.sql new file mode 100644 index 00000000..34c3d392 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/macros/data_quality/verificacao_tipagem.sql @@ -0,0 +1,25 @@ +{% macro test_verificacao_tipagem(model, nome_tabela, nome_coluna, tipo_esperado) %} + with + column_info as ( + select + table_schema, + table_name, -- Nome real da coluna no information_schema + column_name, -- Nome real da coluna no information_schema + data_type + from information_schema.columns + where + table_schema || '.' || table_name = '{{ nome_tabela }}' + and column_name = '{{ nome_coluna }}' + ), + comparison as ( + select + '{{ nome_tabela }}' as nome_tabela, + '{{ nome_coluna }}' as nome_coluna, + '{{ tipo_esperado }}' as tipo_esperado, + data_type as actual_type + from column_info + ) + select * + from comparison + where actual_type != tipo_esperado +{% endmacro %} diff --git a/airflow_lappis/dags/dbt/ipea/models/schema.yml b/airflow_lappis/dags/dbt/ipea/models/schema.yml index cf78cd06..744ab558 100644 --- a/airflow_lappis/dags/dbt/ipea/models/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/schema.yml @@ -17,25 +17,268 @@ models: - name: id description: > Identificador único do contrato, utilizado para referenciar o contrato em outras tabelas e análises. - + data_tests: + - row_count_match: + source_table: compras_gov.contratos + target_table: contratos.contratos + - verificacao_tipagem: + nome_tabela: 'contratos.contratos' + nome_coluna: 'num_parcelas' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'contratos.contratos' + nome_coluna: 'valor_inicial' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.contratos' + nome_coluna: 'valor_global' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.contratos' + nome_coluna: 'valor_parcela' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.contratos' + nome_coluna: 'valor_acumulado' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.contratos' + nome_coluna: 'data_assinatura' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'contratos.contratos' + nome_coluna: 'data_publicacao' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'contratos.contratos' + nome_coluna: 'data_proposta_comercial' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'contratos.contratos' + nome_coluna: 'vigencia_inicio' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'contratos.contratos' + nome_coluna: 'vigencia_fim' + tipo_esperado: 'date' + - name: cronogramas description: > Essa tabela contém informações sobre as despesas mensais programadas cada contrato. + data_tests: + - verificacao_tipagem: + nome_tabela: 'contratos.cronogramas' + nome_coluna: 'id' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'contratos.cronogramas' + nome_coluna: 'contrato_id' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'contratos.cronogramas' + nome_coluna: 'mesref' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'contratos.cronogramas' + nome_coluna: 'anoref' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'contratos.cronogramas' + nome_coluna: 'valor' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.cronogramas' + nome_coluna: 'vencimento' + tipo_esperado: 'date' - name: faturas description: "Essa tabela contém informações sobre as faturas emitidas mensalmente de cada contrato." + data_tests: + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'id' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'contrato_id' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'emissao' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'prazo' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'vencimento' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'valor' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'juros' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'multa' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'glosa' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'valorliquido' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'protocolo' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'ateste' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'mesref' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'anoref' + tipo_esperado: 'integer' - name: empenhos description: > Essa tabela contém informações sobre os empenhos de cada contrato. + data_tests: + - row_count_match: + source_table: compras_gov.empenhos + target_table: contratos.empenhos + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos' + nome_coluna: 'empenhado' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos' + nome_coluna: 'aliquidar' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos' + nome_coluna: 'liquidado' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos' + nome_coluna: 'pago' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos' + nome_coluna: 'rpinscrito' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos' + nome_coluna: 'rpaliquidar' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos' + nome_coluna: 'rpliquidado' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos' + nome_coluna: 'rppago' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos' + nome_coluna: 'data_emissao' + tipo_esperado: 'date' + - name: empenhos_tesouro description: > Essa tabela contém informações sobre movimentação financeira dos empenhos extraídos do SIAFI. + data_tests: + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos_tesouro' + nome_coluna: 'ne_ccor_ano_emissao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos_tesouro' + nome_coluna: 'despesas_empenhadas' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos_tesouro' + nome_coluna: 'despesas_liquidadas' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos_tesouro' + nome_coluna: 'despesas_pagas' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos_tesouro' + nome_coluna: 'restos_a_pagar_inscritos' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos_tesouro' + nome_coluna: 'restos_a_pagar_pagos' + tipo_esperado: 'numeric' + - name: estagios description: > Essa tabela contém informações sobre os evento de cada empenho discriminados por mês. + data_tests: + - verificacao_tipagem: + nome_tabela: 'contratos.estagios' + nome_coluna: 'natureza_despesa' + tipo_esperado: 'integer' + + - verificacao_tipagem: + nome_tabela: 'contratos.estagios' + nome_coluna: 'natureza_despesa_detalhada' + tipo_esperado: 'integer' + + - verificacao_tipagem: + nome_tabela: 'contratos.estagios' + nome_coluna: 'ano_lancamento' + tipo_esperado: 'integer' + + - verificacao_tipagem: + nome_tabela: 'contratos.estagios' + nome_coluna: 'ne_ccor_ano_emissao' + tipo_esperado: 'integer' + + - verificacao_tipagem: + nome_tabela: 'contratos.estagios' + nome_coluna: 'despesas_empenhadas_controle_empenho_saldo_moeda_origem' + tipo_esperado: 'numeric' + + - verificacao_tipagem: + nome_tabela: 'contratos.estagios' + nome_coluna: 'despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem' + tipo_esperado: 'numeric' + + - verificacao_tipagem: + nome_tabela: 'contratos.estagios' + nome_coluna: 'despesas_liquidadas_controle_empenho_saldo_moeda_origem' + tipo_esperado: 'numeric' + + - verificacao_tipagem: + nome_tabela: 'contratos.estagios' + nome_coluna: 'despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem' + tipo_esperado: 'numeric' + + - verificacao_tipagem: + nome_tabela: 'contratos.estagios' + nome_coluna: 'despesas_pagas_controle_empenho_saldo_moeda_origem' + tipo_esperado: 'numeric' + + - verificacao_tipagem: + nome_tabela: 'contratos.estagios' + nome_coluna: 'despesas_pagas_controle_empenho_movim_liquido_moeda_origem' + tipo_esperado: 'numeric' ## Silver - name: contratos_empenhos @@ -78,6 +321,15 @@ models: description: > Dados das programações financeiras extraídas do SIAFI, com informações sobre o tipo de programação, a data de execução e o valor. Essa tabela é atualizada diariamente. + data_tests: + - verificacao_tipagem: + nome_tabela: 'ted.pf_tesouro' + nome_coluna: 'emissao_dia' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'ted.pf_tesouro' + nome_coluna: 'pf_valor_linha' + tipo_esperado: 'numeric' - name: empenhos_plano_acao description: > From 34ebbcf01dc541f36329784c70784369074127c3 Mon Sep 17 00:00:00 2001 From: VictorSzk Date: Mon, 5 May 2025 22:11:45 +0000 Subject: [PATCH 068/317] Feat/gold ted --- .../dags/dbt/ipea/models/sources.yml | 2 +- .../ipea/models/ted_dbt/bronze/nc_tesouro.sql | 30 ++++++++ .../models/ted_dbt/bronze/planos_acao.sql | 30 ++++++++ .../ted_dbt/gold/ted_resumo_orcamentario.sql | 73 +++++++++++++++++++ .../models/ted_dbt/silver/nc_plano_acao.sql | 11 +-- .../ted_dbt/views/num_transf_n_plano_acao.sql | 2 +- 6 files changed, 139 insertions(+), 9 deletions(-) create mode 100644 airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/nc_tesouro.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/planos_acao.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/ted_resumo_orcamentario.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/sources.yml b/airflow_lappis/dags/dbt/ipea/models/sources.yml index 9ad13553..8ba84b9c 100644 --- a/airflow_lappis/dags/dbt/ipea/models/sources.yml +++ b/airflow_lappis/dags/dbt/ipea/models/sources.yml @@ -14,7 +14,7 @@ sources: tables: - name: empenhos_tesouro - name: estagios_tesouro - - name: ncs_tesouro + - name: nc_tesouro - name: pf_tesouro - name: transfere_gov diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/nc_tesouro.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/nc_tesouro.sql new file mode 100644 index 00000000..add24d65 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/nc_tesouro.sql @@ -0,0 +1,30 @@ +with + + notas_credito as ( + select + {{ target.schema }}.parse_date(emissao_mes) as emissao_mes, + to_date(emissao_dia, 'DD/MM/YYYY') as emissao_dia, + nc, + nc_transferencia, + nc_fonte_recursos, + nc_fonte_recursos_descricao, + ptres, + nc_evento, + nc_evento_descricao as nc_evento_descr, + ug_responsavel, + ug_responsavel_descricao, + natureza_despesa as nc_natureza_despesa, + natureza_despesa_detalhada as nc_natureza_despesa_descricao, + plano_interno, + plano_detalhado_descricao1, + plano_detalhado_descricao2, + favorecido_doc, + favorecido_doc_descricao, + replace(nc_valor_linha, ',', '.')::numeric(15, 2) as nc_valor_linha, + {{ parse_financial_value("movimento_liquido") }} as movimento_liquido + from {{ source("siafi", "nc_tesouro") }} + ) + +-- +select * +from notas_credito diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/planos_acao.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/planos_acao.sql new file mode 100644 index 00000000..ef3e4687 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/planos_acao.sql @@ -0,0 +1,30 @@ +with + + planos_acao as ( + select + id_plano_acao, + id_programa, + sigla_unidade_descentralizada, + unidade_descentralizada, + sigla_unidade_responsavel_execucao, + unidade_responsavel_execucao, + vl_total_plano_acao::numeric(15, 2) as vl_total_plano_acao, + to_date(dt_inicio_vigencia, 'YYYY-mm-dd') as dt_inicio_vigencia, + to_date(dt_fim_vigencia, 'YYYY-mm-dd') as dt_fim_vigencia, + tx_objeto_plano_acao, + tx_justificativa_plano_acao, + in_forma_execucao_direta, + in_forma_execucao_particulares, + in_forma_execucao_descentralizada, + tx_situacao_plano_acao, + aa_ano_plano_acao, + vl_beneficiario_especifico::numeric(15, 2) as vl_beneficiario_especifico, + vl_chamamento_publico::numeric(15, 2) as vl_chamamento_publico, + sq_instrumento, + aa_instrumento + from {{ source("transfere_gov", "planos_acao") }} + ) + +-- +select * +from planos_acao diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/ted_resumo_orcamentario.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/ted_resumo_orcamentario.sql new file mode 100644 index 00000000..75ae00d5 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/ted_resumo_orcamentario.sql @@ -0,0 +1,73 @@ +-- Armazenando apenas os valores independentes das tabelas +-- valores calculados serão computados no dashboard +with + + -- Valor firmado + valor_firmado_tb as ( + select id_plano_acao, vl_total_plano_acao as valor_firmado_tb + from {{ ref("planos_acao") }} + ), + + -- Orçamento recebido + -- Orçamento devolvido + valores_orcamentos_tb as ( + select + plano_acao as id_plano_acao, + sum( + case when nc_evento not in ('300301', '300307') then nc_valor else 0 end + ) as orcamento_recebido, + sum( + case when nc_evento in ('300301', '300307') then nc_valor else 0 end + ) as orcamento_devolvido + from {{ ref("nc_plano_acao") }} + where ptres not in ('-9') + group by id_plano_acao + ), + -- Destaque orçamentario = Orçamento recebido - Orçamento devolvido + -- Destaque a receber = Valor firmado - Destaque orçamentario + -- Empenhado + -- Empenho anulado + -- Utilizado/pago + valores_empenhados_tb as ( + select + plano_acao as id_plano_acao, + sum( + case when despesas_empenhadas > 0 then despesas_empenhadas else 0 end + ) as empenhado, + sum( + case when despesas_empenhadas < 0 then - despesas_empenhadas else 0 end + ) as empenho_anulado, + sum(despesas_pagas) as despesas_pagas + from {{ ref("empenhos_plano_acao") }} + where abs(despesas_empenhadas) > 0 + group by id_plano_acao + ), + + -- Saldo empenho = Empenhado - Empenho anulado - Utilizado/pago + -- Financeiro recebido + -- Financeiro devolvido + -- Utilizado/pago + valores_financeiro_tb as ( + select + plano_acao as id_plano_acao, + sum( + case when pf_acao = 'TRANSFERENCIA' then pf_valor_linha else 0 end + ) as financeiro_recebido, + sum( + case when pf_acao = 'DEVOLUCAO' then pf_valor_linha else 0 end + ) as financeiro_devolvido, + sum( + case when pf_acao = 'CANCELAMENTO' then pf_valor_linha else 0 end + ) as financeiro_cancelado + from {{ ref("pf_plano_acao") }} + group by plano_acao + ) +-- Saldo financeiro = Financeiro recebido - Financeiro devolvido - Utilizado/pago +-- Financeiro a receber = Valor firmado - Financeiro recebido + Financeiro devolvido +-- Final +select * +from valor_firmado_tb +left join valores_orcamentos_tb using (id_plano_acao) +left join valores_empenhados_tb using (id_plano_acao) +left join valores_financeiro_tb using (id_plano_acao) +where id_plano_acao is not null diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/nc_plano_acao.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/nc_plano_acao.sql index 313a16fe..fe4f1c7f 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/nc_plano_acao.sql +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/nc_plano_acao.sql @@ -1,10 +1,6 @@ with - raw_data as ( - select * - from {{ source("siafi", "ncs_tesouro") }} nt - where nc_transferencia != '-8' - ), + raw_data as (select * from {{ ref("nc_tesouro") }} nt where nc_transferencia != '-8'), planos_de_acao as ( select distinct * @@ -20,12 +16,13 @@ with nc_fonte_recursos, ptres, nc_natureza_despesa, + nc_evento, nc_evento_descr, -- aplica o sinal correto a depender do tipo de evento case when nc_evento in ('300302', '300308', '300311', '300083') - then (-1) * replace(nc_valor_linha, ',', '.')::numeric - else replace(nc_valor_linha, ',', '.')::numeric + then (-1) * nc_valor_linha + else nc_valor_linha end as nc_valor from raw_data rd left join planos_de_acao pda on rd.nc_transferencia = pda.num_transf diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/views/num_transf_n_plano_acao.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/views/num_transf_n_plano_acao.sql index 1b872140..49a3b49b 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/views/num_transf_n_plano_acao.sql +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/views/num_transf_n_plano_acao.sql @@ -11,7 +11,7 @@ with nc_siafi as ( select distinct left(nc, 6) as ug, right(nc, 12) as nc, nt.nc_transferencia as num_transf - from {{ source("siafi", "ncs_tesouro") }} nt + from {{ source("siafi", "nc_tesouro") }} nt where nc_transferencia != '-8' ), From 04f49c2b659be47c5bb50e447352207391119e63 Mon Sep 17 00:00:00 2001 From: Joyce Dionizio Date: Tue, 6 May 2025 13:30:18 +0000 Subject: [PATCH 069/317] Feat/siape lista uorgs --- .../lista_uorgs_siape_ingest_dag.py | 76 +++++++++++++++++++ airflow_lappis/plugins/cliente_siape.py | 34 +++++++++ .../templates/siape/listaUorgs.xml.j2 | 3 +- 3 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 airflow_lappis/dags/data_ingest/lista_uorgs_siape_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/lista_uorgs_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/lista_uorgs_siape_ingest_dag.py new file mode 100644 index 00000000..44706ec5 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/lista_uorgs_siape_ingest_dag.py @@ -0,0 +1,76 @@ +import os +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from postgres_helpers import get_postgres_conn +from cliente_siape import ClienteSiape +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Joyce", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["siape", "lista_uorgs"], +) +def siape_lista_uorgs_dag() -> None: + """ + DAG que extrai a lista de UORGs do SIAPE via API SOAP + e insere no schema 'siape', tabela 'lista_uorgs'. + """ + + @task + def fetch_and_store_lista_uorgs() -> None: + logging.info("[siape_lista_uorgs_dag] Iniciando extração da lista de UORGs") + + cliente_siape = ClienteSiape() + + context = { + "siglaSistema": "PETRVS-IPEA", + "nomeSistema": "PDG-PETRVS-IPEA", + "senha": os.getenv("SIAPE_PASSWORD_USER"), + "cpf": os.getenv("SIAPE_CPF_USER"), + "codOrgao": "45206", + } + + resposta_xml = cliente_siape.call("listaUorgs.xml.j2", context) + + # Define os namespaces e o elemento que queremos extrair + ns = { + "soapenv": "http://schemas.xmlsoap.org/soap/envelope/", + "ns1": "http://servico.wssiapenet", + "ns2": "http://entidade.wssiapenet", + } + + dados_lista = ClienteSiape.parse_xml_to_list( + xml_string=resposta_xml, element_tag="ns2:Uorg", namespaces=ns + ) + + if not dados_lista: + logging.warning("Nenhum dado retornado da API listaUorgs") + return + + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + logging.info("Inserindo dados no banco de dados") + + db.insert_data( + dados_lista, + table_name="lista_uorgs", + conflict_fields=["codigo"], + primary_key=["codigo"], + schema="siape", + ) + + logging.info("Dados inseridos com sucesso") + + fetch_and_store_lista_uorgs() + + +dag_instance = siape_lista_uorgs_dag() diff --git a/airflow_lappis/plugins/cliente_siape.py b/airflow_lappis/plugins/cliente_siape.py index 2d1c3630..124fe2a7 100755 --- a/airflow_lappis/plugins/cliente_siape.py +++ b/airflow_lappis/plugins/cliente_siape.py @@ -143,3 +143,37 @@ def parse_xml_to_dict(xml_string: str) -> Dict[str, str]: for child in response_elem.iter() if child.text and child.text.strip() } + + @staticmethod + def parse_xml_to_list( + xml_string: str, element_tag: str, namespaces: Dict[str, str] + ) -> list[dict[str, str | None]]: + """ + Parse a SOAP XML response and return a list of dictionaries, + one per repeated element. + + Args: + xml_string (str): SOAP XML response. + element_tag (str): Tag do elemento que se repete (ex: 'ns2:Uorg'). + namespaces (Dict[str, str]): Mapeamento de namespaces. + + Returns: + list[dict[str, str]]: Lista de registros extraídos. + """ + root = ET.fromstring(xml_string) + body = root.find("soapenv:Body", namespaces) + if body is None: + return [] + + response_elem = list(body)[0] + items = response_elem.findall(f".//{element_tag}", namespaces) + + resultado = [] + for item in items: + row = {} + for elem in item: + tag = elem.tag.split("}")[-1] + row[tag] = elem.text.strip() if elem.text else None + resultado.append(row) + + return resultado diff --git a/airflow_lappis/templates/siape/listaUorgs.xml.j2 b/airflow_lappis/templates/siape/listaUorgs.xml.j2 index 57cf959a..d17ea764 100644 --- a/airflow_lappis/templates/siape/listaUorgs.xml.j2 +++ b/airflow_lappis/templates/siape/listaUorgs.xml.j2 @@ -1,4 +1,3 @@ - @@ -8,7 +7,7 @@ {{ senha }} {{ cpf }} {{ codOrgao }} - {{ codUorg }} + 00000000 From 1e54c79354e6621f64e4db70bd66cda63a538aef Mon Sep 17 00:00:00 2001 From: Joyce Dionizio Date: Tue, 6 May 2025 14:00:14 +0000 Subject: [PATCH 070/317] feat(siape): adiciona dag lista de servidore --- .../lista_servidores_siape_ingest_dag.py | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/lista_servidores_siape_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/lista_servidores_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/lista_servidores_siape_ingest_dag.py new file mode 100644 index 00000000..92339fbc --- /dev/null +++ b/airflow_lappis/dags/data_ingest/lista_servidores_siape_ingest_dag.py @@ -0,0 +1,80 @@ +import os +import logging +from datetime import datetime, timedelta +from airflow.decorators import dag, task +from postgres_helpers import get_postgres_conn +from cliente_siape import ClienteSiape +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Joyce", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["siape", "lista_servidores"], +) +def siape_lista_servidores_dag() -> None: + + @task + def fetch_and_store_lista_servidores() -> None: + logging.info("Iniciando extração de servidores por UORG") + cliente_siape = ClienteSiape() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + query = "SELECT codigo FROM siape.lista_uorgs" + codigos_uorg = [row[0] for row in db.execute_query(query)] + logging.info(f"Total de UORGs encontradas: {len(codigos_uorg)}") + + ns = { + "soapenv": "http://schemas.xmlsoap.org/soap/envelope/", + "ns1": "http://servico.wssiapenet", + "ns2": "http://entidade.wssiapenet", + } + + for cod in codigos_uorg: + try: + context = { + "siglaSistema": "PETRVS-IPEA", + "nomeSistema": "PDG-PETRVS-IPEA", + "senha": os.getenv("SIAPE_PASSWORD_USER"), + "cpf": os.getenv("SIAPE_CPF_USER"), + "codOrgao": "45206", + "codUorg": cod, + } + + resposta_xml = cliente_siape.call("listaServidores.xml.j2", context) + dados = ClienteSiape.parse_xml_to_list( + xml_string=resposta_xml, element_tag="ns2:Servidor", namespaces=ns + ) + + if not dados: + logging.info(f"Nenhum servidor encontrado para UORG {cod}") + continue + + for row in dados: + row["codUorg"] = str(cod) + + db.insert_data( + dados, + table_name="lista_servidores", + conflict_fields=["cpf", "codUorg"], + primary_key=["cpf", "codUorg"], + schema="siape", + ) + + logging.info(f"{len(dados)} servidores inseridos para UORG {cod}") + + except Exception as e: + logging.error(f"Erro ao processar UORG {cod}: {e}") + continue + + fetch_and_store_lista_servidores() + + +dag_instance = siape_lista_servidores_dag() From 55dc18de7e6b18b83c071e93db8810a215fda23e Mon Sep 17 00:00:00 2001 From: Joyce Date: Tue, 6 May 2025 17:25:25 -0300 Subject: [PATCH 071/317] refactor(dag): adiciona logica para varios cpfs --- .../dags/data_ingest/dados_funcionais_dag.py | 72 ------------------ .../dados_funcionais_siape_ingest_dag.py | 75 +++++++++++++++++++ 2 files changed, 75 insertions(+), 72 deletions(-) delete mode 100644 airflow_lappis/dags/data_ingest/dados_funcionais_dag.py create mode 100644 airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/dados_funcionais_dag.py b/airflow_lappis/dags/data_ingest/dados_funcionais_dag.py deleted file mode 100644 index 4435ebd3..00000000 --- a/airflow_lappis/dags/data_ingest/dados_funcionais_dag.py +++ /dev/null @@ -1,72 +0,0 @@ -import os -import logging -from airflow.decorators import dag, task -from datetime import datetime, timedelta -from postgres_helpers import get_postgres_conn -from cliente_siape import ClienteSiape -from cliente_postgres import ClientPostgresDB - - -@dag( - schedule_interval="@daily", - start_date=datetime(2023, 1, 1), - catchup=False, - default_args={ - "owner": "Joyce", - "retries": 1, - "retry_delay": timedelta(minutes=5), - }, - tags=["siape", "dados_funcionais"], -) -def siape_dados_funcionais_dag() -> None: - """ - DAG que extrai dados funcionais do SIAPE via API SOAP - e insere no schema 'siape', tabela 'dados_funcionais'. - """ - - @task - def fetch_and_store_dados_funcionais() -> None: - logging.info( - "[siape_dados_funcionais_dag] Iniciando extração dos dados funcionais" - ) - - cliente_siape = ClienteSiape() - - context = { - "siglaSistema": "PETRVS-IPEA", - "nomeSistema": "PDG-PETRVS-IPEA", - "senha": os.getenv("SIAPE_PASSWORD_USER"), - "cpf": os.getenv("SIAPE_CPF_USER"), - "codOrgao": "45206", - "parmExistPag": "b", - "parmTipoVinculo": "c", - } - - resposta_xml = cliente_siape.call("consultaDadosFuncionais.xml.j2", context) - dados_dict = ClienteSiape.parse_xml_to_dict(resposta_xml) - - if not dados_dict: - logging.warning( - "[siape_dados_funcionais_dag] Nenhum dado retornado da API SIAPE" - ) - return - - postgres_conn_str = get_postgres_conn() - db = ClientPostgresDB(postgres_conn_str) - - logging.info("[siape_dados_funcionais_dag] Inserindo dados no banco de dados") - - db.insert_data( - [dados_dict], - table_name="dados_funcionais", - conflict_fields=None, - primary_key=None, - schema="siape", - ) - - logging.info("[siape_dados_funcionais_dag] Dados inseridos com sucesso") - - fetch_and_store_dados_funcionais() - - -dag_instance = siape_dados_funcionais_dag() diff --git a/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py new file mode 100644 index 00000000..b6200299 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py @@ -0,0 +1,75 @@ +import os +import logging +from datetime import datetime, timedelta +from airflow.decorators import dag, task +from postgres_helpers import get_postgres_conn +from cliente_siape import ClienteSiape +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Joyce", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["siape", "dados_funcionais"], +) +def siape_dados_funcionais_dag() -> None: + + @task + def fetch_and_store_dados_funcionais() -> None: + logging.info("Iniciando extração de dados funcionais por CPF") + cliente_siape = ClienteSiape() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + query = "SELECT DISTINCT cpf FROM siape.lista_servidores WHERE cpf IS NOT NULL" + cpfs = [row[0] for row in db.execute_query(query)] + logging.info(f"Total de CPFs encontrados: {len(cpfs)}") + + for cpf in cpfs: + try: + context = { + "siglaSistema": "PETRVS-IPEA", + "nomeSistema": "PDG-PETRVS-IPEA", + "senha": os.getenv("SIAPE_PASSWORD_USER"), + "cpf": cpf, + "codOrgao": "45206", + "parmExistPag": "b", + "parmTipoVinculo": "c", + } + + resposta_xml = cliente_siape.call( + "consultaDadosFuncionais.xml.j2", context + ) + dados = ClienteSiape.parse_xml_to_dict(resposta_xml) + + if not dados: + logging.warning(f"Nenhum dado funcional encontrado para CPF {cpf}") + continue + + dados["cpf"] = cpf + dados["data_extracao"] = datetime.utcnow().isoformat() + + db.insert_data( + [dados], + table_name="dados_funcionais", + conflict_fields=["cpf"], + primary_key=["cpf"], + schema="siape", + ) + + logging.info(f"Dado funcional inserido para CPF {cpf}") + + except Exception as e: + logging.error(f"Erro ao processar CPF {cpf}: {e}") + continue + + fetch_and_store_dados_funcionais() + + +dag_instance = siape_dados_funcionais_dag() From a5272955d42cb80b80f0145ea1214ad08ab80a26 Mon Sep 17 00:00:00 2001 From: Joyce Date: Tue, 6 May 2025 18:23:58 -0300 Subject: [PATCH 072/317] fix(dag): corrige coluna inexistente --- .../dags/data_ingest/dados_funcionais_siape_ingest_dag.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py index b6200299..8b27135f 100644 --- a/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py @@ -52,14 +52,11 @@ def fetch_and_store_dados_funcionais() -> None: logging.warning(f"Nenhum dado funcional encontrado para CPF {cpf}") continue - dados["cpf"] = cpf - dados["data_extracao"] = datetime.utcnow().isoformat() - db.insert_data( [dados], table_name="dados_funcionais", - conflict_fields=["cpf"], - primary_key=["cpf"], + conflict_fields=["matriculaSiape"], + primary_key=["matriculaSiape"], schema="siape", ) From 74cf8102e4402ee10c74562de982170ad9c6f0bd Mon Sep 17 00:00:00 2001 From: Joyce Date: Tue, 6 May 2025 20:19:48 -0300 Subject: [PATCH 073/317] refactor(table): altera nome da tabela --- .../dags/data_ingest/dados_funcionais_siape_ingest_dag.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py index 8b27135f..2bc88ea7 100644 --- a/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py @@ -54,7 +54,7 @@ def fetch_and_store_dados_funcionais() -> None: db.insert_data( [dados], - table_name="dados_funcionais", + table_name="dados_funcionais_siape", conflict_fields=["matriculaSiape"], primary_key=["matriculaSiape"], schema="siape", From 57b98a18ac5e1eb181175976b6047bb7977bfa71 Mon Sep 17 00:00:00 2001 From: Joyce Date: Wed, 7 May 2025 10:42:30 -0300 Subject: [PATCH 074/317] feat(dag): adiciona dag para excluir tabelas --- .../dags/data_ingest/drop_tables_dag.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/drop_tables_dag.py diff --git a/airflow_lappis/dags/data_ingest/drop_tables_dag.py b/airflow_lappis/dags/data_ingest/drop_tables_dag.py new file mode 100644 index 00000000..91f689bf --- /dev/null +++ b/airflow_lappis/dags/data_ingest/drop_tables_dag.py @@ -0,0 +1,43 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from postgres_helpers import get_postgres_conn +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval=None, + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "admin", + "retries": 0, + "retry_delay": timedelta(minutes=5), + }, + tags=["siape", "admin", "drop"], +) +def drop_siape_tabelas_dag() -> None: + """ + DAG para remover tabelas antigas do schema siape. + Útil para limpar estrutura antes de reingestão. + """ + + @task + def drop_tabelas() -> None: + logging.info("Iniciando remoção de tabelas antigas do schema siape") + conn_str = get_postgres_conn() + db = ClientPostgresDB(conn_str) + + tabelas = ["dados_funcionais", "dados_funcionais_siape"] + + for tabela in tabelas: + try: + db.drop_table_if_exists(tabela, schema="siape") + logging.info(f"Tabela {tabela} removida com sucesso.") + except Exception as e: + logging.error(f"Erro ao remover tabela {tabela}: {e}") + + drop_tabelas() + + +dag_instance = drop_siape_tabelas_dag() From 9d9237aaa9216d5c93cc02df2dacabe0b781d065 Mon Sep 17 00:00:00 2001 From: Joyce Date: Wed, 7 May 2025 11:22:33 -0300 Subject: [PATCH 075/317] fix(dag): corrige erro na ingestao dos dados --- .../dags/data_ingest/dados_funcionais_siape_ingest_dag.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py index 2bc88ea7..687740d9 100644 --- a/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py @@ -52,9 +52,15 @@ def fetch_and_store_dados_funcionais() -> None: logging.warning(f"Nenhum dado funcional encontrado para CPF {cpf}") continue + db.alter_table( + data=dados, + table_name="dados_funcionais_siape", + schema="siape", + ) + db.insert_data( [dados], - table_name="dados_funcionais_siape", + table_name="dados_funcionais", conflict_fields=["matriculaSiape"], primary_key=["matriculaSiape"], schema="siape", From d697d879a0b36fa7758b89e890356c866dff0df6 Mon Sep 17 00:00:00 2001 From: Joyce Date: Wed, 7 May 2025 12:49:54 -0300 Subject: [PATCH 076/317] fix(dag): corrige nome da tabela --- .../dags/data_ingest/dados_funcionais_siape_ingest_dag.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py index 687740d9..a4495255 100644 --- a/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py @@ -54,7 +54,7 @@ def fetch_and_store_dados_funcionais() -> None: db.alter_table( data=dados, - table_name="dados_funcionais_siape", + table_name="dados_funcionais", schema="siape", ) From 16581ff54b90f424c870bf20560fd39d968e5a44 Mon Sep 17 00:00:00 2001 From: Joyce Dionizio Date: Wed, 7 May 2025 16:45:32 +0000 Subject: [PATCH 077/317] Feat/siape dados pessoais --- .../dados_funcionais_siape_ingest_dag.py | 6 +- .../dados_pessoais_siape_ingest_dag.py | 82 +++++++++++++++++++ 2 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 airflow_lappis/dags/data_ingest/dados_pessoais_siape_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py index a4495255..a134287b 100644 --- a/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py @@ -52,6 +52,8 @@ def fetch_and_store_dados_funcionais() -> None: logging.warning(f"Nenhum dado funcional encontrado para CPF {cpf}") continue + dados["cpf"] = cpf + db.alter_table( data=dados, table_name="dados_funcionais", @@ -61,8 +63,8 @@ def fetch_and_store_dados_funcionais() -> None: db.insert_data( [dados], table_name="dados_funcionais", - conflict_fields=["matriculaSiape"], - primary_key=["matriculaSiape"], + conflict_fields=["cpf"], + primary_key=["cpf"], schema="siape", ) diff --git a/airflow_lappis/dags/data_ingest/dados_pessoais_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_pessoais_siape_ingest_dag.py new file mode 100644 index 00000000..26c42bc7 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/dados_pessoais_siape_ingest_dag.py @@ -0,0 +1,82 @@ +import os +import logging +from datetime import datetime, timedelta +from airflow.decorators import dag, task +from postgres_helpers import get_postgres_conn +from cliente_siape import ClienteSiape +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Joyce", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["siape", "dados_pessoais"], +) +def siape_dados_pessoais_dag() -> None: + """ + DAG que consome o endpoint consultaDadosPessoais da API SIAPE + e armazena os dados pessoais de servidores públicos no schema 'siape'. + """ + + @task + def fetch_and_store_dados_pessoais() -> None: + logging.info("Iniciando extração de dados pessoais por CPF") + cliente_siape = ClienteSiape() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + query = "SELECT DISTINCT cpf FROM siape.lista_servidores WHERE cpf IS NOT NULL" + cpfs = [row[0] for row in db.execute_query(query)] + logging.info(f"Total de CPFs encontrados: {len(cpfs)}") + + for cpf in cpfs: + try: + context = { + "siglaSistema": "PETRVS-IPEA", + "nomeSistema": "PDG-PETRVS-IPEA", + "senha": os.getenv("SIAPE_PASSWORD_USER"), + "cpf": cpf, + "codOrgao": "45206", + "parmExistPag": "b", + "parmTipoVinculo": "c", + } + + resposta_xml = cliente_siape.call("consultaDadosPessoais.xml.j2", context) + dados = ClienteSiape.parse_xml_to_dict(resposta_xml) + + if not dados: + logging.warning(f"Nenhum dado pessoal encontrado para CPF {cpf}") + continue + + dados["cpf"] = cpf + + db.alter_table( + data=dados, + table_name="dados_pessoais", + schema="siape", + ) + + db.insert_data( + [dados], + table_name="dados_pessoais", + conflict_fields=["cpf"], + primary_key=["cpf"], + schema="siape", + ) + + logging.info(f"Dado pessoal inserido para CPF {cpf}") + + except Exception as e: + logging.error(f"Erro ao processar CPF {cpf}: {e}") + continue + + fetch_and_store_dados_pessoais() + + +dag_instance = siape_dados_pessoais_dag() From 35953fe294a9b5a4000d52f2a5f3563a501d4b41 Mon Sep 17 00:00:00 2001 From: Joyce Dionizio Date: Wed, 7 May 2025 17:59:47 +0000 Subject: [PATCH 078/317] Feat/dags ingest siape --- ..._afastamento_historico_siape_ingest_dag.py | 95 +++++++++++++++++++ .../dados_afastamento_siape_ingest_dag.py | 82 ++++++++++++++++ .../dados_curriculo_siape_ingest_dag.py | 82 ++++++++++++++++ .../dados_dependentes_siape_ingest_dag.py | 91 ++++++++++++++++++ .../dados_escolares_siape_ingest_dag.py | 82 ++++++++++++++++ .../dados_financeiros_siape_dag.py | 82 ++++++++++++++++ .../data_ingest/dados_pa_siape_ingest_dag.py | 80 ++++++++++++++++ .../dados_uorg_siape_ingest_dag.py | 80 ++++++++++++++++ ..._aposentadoria_parcial_siape_ingest_dag.py | 93 ++++++++++++++++++ .../lista_aposentadoria_siape_ingest_dag.py | 81 ++++++++++++++++ 10 files changed, 848 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/dados_afastamento_historico_siape_ingest_dag.py create mode 100644 airflow_lappis/dags/data_ingest/dados_afastamento_siape_ingest_dag.py create mode 100644 airflow_lappis/dags/data_ingest/dados_curriculo_siape_ingest_dag.py create mode 100644 airflow_lappis/dags/data_ingest/dados_dependentes_siape_ingest_dag.py create mode 100644 airflow_lappis/dags/data_ingest/dados_escolares_siape_ingest_dag.py create mode 100644 airflow_lappis/dags/data_ingest/dados_financeiros_siape_dag.py create mode 100644 airflow_lappis/dags/data_ingest/dados_pa_siape_ingest_dag.py create mode 100644 airflow_lappis/dags/data_ingest/dados_uorg_siape_ingest_dag.py create mode 100644 airflow_lappis/dags/data_ingest/lista_aposentadoria_parcial_siape_ingest_dag.py create mode 100644 airflow_lappis/dags/data_ingest/lista_aposentadoria_siape_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/dados_afastamento_historico_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_afastamento_historico_siape_ingest_dag.py new file mode 100644 index 00000000..afea1a21 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/dados_afastamento_historico_siape_ingest_dag.py @@ -0,0 +1,95 @@ +import os +import logging +from datetime import datetime, timedelta +from airflow.decorators import dag, task +from postgres_helpers import get_postgres_conn +from cliente_siape import ClienteSiape +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Joyce", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["siape", "afastamento_historico"], +) +def siape_afastamento_historico_dag() -> None: + """ + DAG que consome o endpoint consultaDadosAfastamentoHistorico da API SIAPE + e armazena os dados de afastamentos antigos dos servidores no schema 'siape'. + """ + + @task + def fetch_and_store_afastamento_historico() -> None: + logging.info("Iniciando extração de dados de afastamento histórico por CPF") + cliente_siape = ClienteSiape() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + query = "SELECT DISTINCT cpf FROM siape.lista_servidores WHERE cpf IS NOT NULL" + cpfs = [row[0] for row in db.execute_query(query)] + logging.info(f"Total de CPFs encontrados: {len(cpfs)}") + + ns = { + "soapenv": "http://schemas.xmlsoap.org/soap/envelope/", + "ns1": "http://servico.wssiapenet", + "ns2": "http://entidade.wssiapenet", + } + + for cpf in cpfs: + try: + context = { + "siglaSistema": "PETRVS-IPEA", + "nomeSistema": "PDG-PETRVS-IPEA", + "senha": os.getenv("SIAPE_PASSWORD_USER"), + "cpf": cpf, + "codOrgao": "45206", + "parmExistPag": "b", + "parmTipoVinculo": "c", + } + + resposta_xml = cliente_siape.call( + "consultaDadosAfastamentoHistorico.xml.j2", context + ) + dados = ClienteSiape.parse_xml_to_list( + xml_string=resposta_xml, + element_tag="ns2:AfastamentoHistorico", + namespaces=ns, + ) + + if not dados: + logging.info(f"Nenhum dado de afastamento histórico para CPF {cpf}") + continue + + for row in dados: + row["cpf"] = cpf + + db.alter_table( + data=dados[0], + table_name="afastamento_historico", + schema="siape", + ) + + db.insert_data( + dados, + table_name="afastamento_historico", + conflict_fields=None, + primary_key=None, + schema="siape", + ) + + logging.info(f"{len(dados)} registros inseridos para CPF {cpf}") + + except Exception as e: + logging.error(f"Erro ao processar CPF {cpf}: {e}") + continue + + fetch_and_store_afastamento_historico() + + +dag_instance = siape_afastamento_historico_dag() diff --git a/airflow_lappis/dags/data_ingest/dados_afastamento_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_afastamento_siape_ingest_dag.py new file mode 100644 index 00000000..bd0d1bc1 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/dados_afastamento_siape_ingest_dag.py @@ -0,0 +1,82 @@ +import os +import logging +from datetime import datetime, timedelta +from airflow.decorators import dag, task +from postgres_helpers import get_postgres_conn +from cliente_siape import ClienteSiape +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Joyce", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["siape", "dados_afastamento"], +) +def siape_dados_afastamento_dag() -> None: + """ + DAG que consome o endpoint consultaDadosAfastamento da API SIAPE + e armazena dados de afastamento atuais no schema 'siape'. + """ + + @task + def fetch_and_store_dados_afastamento() -> None: + logging.info("Iniciando extração de dados de afastamento por CPF") + cliente_siape = ClienteSiape() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + query = "SELECT DISTINCT cpf FROM siape.lista_servidores WHERE cpf IS NOT NULL" + cpfs = [row[0] for row in db.execute_query(query)] + logging.info(f"Total de CPFs encontrados: {len(cpfs)}") + + for cpf in cpfs: + try: + context = { + "siglaSistema": "PETRVS-IPEA", + "nomeSistema": "PDG-PETRVS-IPEA", + "senha": os.getenv("SIAPE_PASSWORD_USER"), + "cpf": cpf, + "codOrgao": "45206", + "parmExistPag": "b", + "parmTipoVinculo": "c", + } + + resposta_xml = cliente_siape.call( + "consultaDadosAfastamento.xml.j2", context + ) + dados = ClienteSiape.parse_xml_to_dict(resposta_xml) + + if not dados: + logging.warning(f"Nenhum dado de afastamento para CPF {cpf}") + continue + + db.alter_table( + data=dados, + table_name="dados_afastamento", + schema="siape", + ) + + db.insert_data( + [dados], + table_name="dados_afastamento", + conflict_fields=["cpf"], + primary_key=["cpf"], + schema="siape", + ) + + logging.info(f"Dado de afastamento inserido para CPF {cpf}") + + except Exception as e: + logging.error(f"Erro ao processar CPF {cpf}: {e}") + continue + + fetch_and_store_dados_afastamento() + + +dag_instance = siape_dados_afastamento_dag() diff --git a/airflow_lappis/dags/data_ingest/dados_curriculo_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_curriculo_siape_ingest_dag.py new file mode 100644 index 00000000..1b47751b --- /dev/null +++ b/airflow_lappis/dags/data_ingest/dados_curriculo_siape_ingest_dag.py @@ -0,0 +1,82 @@ +import os +import logging +from datetime import datetime, timedelta +from airflow.decorators import dag, task +from postgres_helpers import get_postgres_conn +from cliente_siape import ClienteSiape +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Joyce", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["siape", "dados_curriculo"], +) +def siape_dados_curriculo_dag() -> None: + """ + DAG que consome o endpoint consultaDadosCurriculo da API SIAPE + e armazena os dados de currículo dos servidores no schema 'siape'. + """ + + @task + def fetch_and_store_dados_curriculo() -> None: + logging.info("Iniciando extração de dados de currículo por CPF") + cliente_siape = ClienteSiape() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + query = "SELECT DISTINCT cpf FROM siape.lista_servidores WHERE cpf IS NOT NULL" + cpfs = [row[0] for row in db.execute_query(query)] + logging.info(f"Total de CPFs encontrados: {len(cpfs)}") + + for cpf in cpfs: + try: + context = { + "siglaSistema": "PETRVS-IPEA", + "nomeSistema": "PDG-PETRVS-IPEA", + "senha": os.getenv("SIAPE_PASSWORD_USER"), + "cpf": cpf, + "codOrgao": "45206", + "parmExistPag": "b", + "parmTipoVinculo": "c", + } + + resposta_xml = cliente_siape.call( + "consultaDadosCurriculo.xml.j2", context + ) + dados = ClienteSiape.parse_xml_to_dict(resposta_xml) + + if not dados: + logging.warning(f"Nenhum dado de currículo encontrado para CPF {cpf}") + continue + + db.alter_table( + data=dados, + table_name="dados_curriculo", + schema="siape", + ) + + db.insert_data( + [dados], + table_name="dados_curriculo", + conflict_fields=["cpf"], + primary_key=["cpf"], + schema="siape", + ) + + logging.info(f"Dado de currículo inserido para CPF {cpf}") + + except Exception as e: + logging.error(f"Erro ao processar CPF {cpf}: {e}") + continue + + fetch_and_store_dados_curriculo() + + +dag_instance = siape_dados_curriculo_dag() diff --git a/airflow_lappis/dags/data_ingest/dados_dependentes_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_dependentes_siape_ingest_dag.py new file mode 100644 index 00000000..ceb192b6 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/dados_dependentes_siape_ingest_dag.py @@ -0,0 +1,91 @@ +import os +import logging +from datetime import datetime, timedelta +from airflow.decorators import dag, task +from postgres_helpers import get_postgres_conn +from cliente_siape import ClienteSiape +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Joyce", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["siape", "dados_dependentes"], +) +def siape_dados_dependentes_dag() -> None: + """ + DAG que consome o endpoint consultaDadosDependentes da API SIAPE + e armazena os dados de dependentes dos servidores no schema 'siape'. + """ + + @task + def fetch_and_store_dados_dependentes() -> None: + logging.info("Iniciando extração de dados de dependentes por CPF") + cliente_siape = ClienteSiape() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + query = "SELECT DISTINCT cpf FROM siape.lista_servidores WHERE cpf IS NOT NULL" + cpfs = [row[0] for row in db.execute_query(query)] + logging.info(f"Total de CPFs encontrados: {len(cpfs)}") + + ns = { + "soapenv": "http://schemas.xmlsoap.org/soap/envelope/", + "ns2": "http://entidade.wssiapenet", + } + + for cpf in cpfs: + try: + context = { + "siglaSistema": "PETRVS-IPEA", + "nomeSistema": "PDG-PETRVS-IPEA", + "senha": os.getenv("SIAPE_PASSWORD_USER"), + "cpf": cpf, + "codOrgao": "45206", + "parmExistPag": "b", + "parmTipoVinculo": "c", + } + + resposta_xml = cliente_siape.call( + "consultaDadosDependentes.xml.j2", context + ) + dados = ClienteSiape.parse_xml_to_list( + xml_string=resposta_xml, + element_tag="ns2:Dependente", + namespaces=ns, + ) + + if not dados: + logging.warning(f"Nenhum dependente encontrado para CPF {cpf}") + continue + + db.alter_table( + data=dados[0], + table_name="dados_dependentes", + schema="siape", + ) + + db.insert_data( + dados, + table_name="dados_dependentes", + conflict_fields=["cpf", "nomeDependente"], + primary_key=["cpf", "nomeDependente"], + schema="siape", + ) + + logging.info(f"{len(dados)} dependente(s) inserido(s) para CPF {cpf}") + + except Exception as e: + logging.error(f"Erro ao processar CPF {cpf}: {e}") + continue + + fetch_and_store_dados_dependentes() + + +dag_instance = siape_dados_dependentes_dag() diff --git a/airflow_lappis/dags/data_ingest/dados_escolares_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_escolares_siape_ingest_dag.py new file mode 100644 index 00000000..bef240e6 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/dados_escolares_siape_ingest_dag.py @@ -0,0 +1,82 @@ +import os +import logging +from datetime import datetime, timedelta +from airflow.decorators import dag, task +from postgres_helpers import get_postgres_conn +from cliente_siape import ClienteSiape +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Joyce", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["siape", "dados_escolares"], +) +def siape_dados_escolares_dag() -> None: + """ + DAG que consome o endpoint consultaDadosEscolares da API SIAPE + e armazena os dados de escolaridade dos servidores no schema 'siape'. + """ + + @task + def fetch_and_store_dados_escolares() -> None: + logging.info("Iniciando extração de dados escolares por CPF") + cliente_siape = ClienteSiape() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + query = "SELECT DISTINCT cpf FROM siape.lista_servidores WHERE cpf IS NOT NULL" + cpfs = [row[0] for row in db.execute_query(query)] + logging.info(f"Total de CPFs encontrados: {len(cpfs)}") + + for cpf in cpfs: + try: + context = { + "siglaSistema": "PETRVS-IPEA", + "nomeSistema": "PDG-PETRVS-IPEA", + "senha": os.getenv("SIAPE_PASSWORD_USER"), + "cpf": cpf, + "codOrgao": "45206", + "parmExistPag": "b", + "parmTipoVinculo": "c", + } + + resposta_xml = cliente_siape.call( + "consultaDadosEscolares.xml.j2", context + ) + dados = ClienteSiape.parse_xml_to_dict(resposta_xml) + + if not dados: + logging.warning(f"Nenhum dado escolar encontrado para CPF {cpf}") + continue + + db.alter_table( + data=dados, + table_name="dados_escolares", + schema="siape", + ) + + db.insert_data( + [dados], + table_name="dados_escolares", + conflict_fields=["cpf"], + primary_key=["cpf"], + schema="siape", + ) + + logging.info(f"Dado escolar inserido para CPF {cpf}") + + except Exception as e: + logging.error(f"Erro ao processar CPF {cpf}: {e}") + continue + + fetch_and_store_dados_escolares() + + +dag_instance = siape_dados_escolares_dag() diff --git a/airflow_lappis/dags/data_ingest/dados_financeiros_siape_dag.py b/airflow_lappis/dags/data_ingest/dados_financeiros_siape_dag.py new file mode 100644 index 00000000..d980a5de --- /dev/null +++ b/airflow_lappis/dags/data_ingest/dados_financeiros_siape_dag.py @@ -0,0 +1,82 @@ +import os +import logging +from datetime import datetime, timedelta +from airflow.decorators import dag, task +from postgres_helpers import get_postgres_conn +from cliente_siape import ClienteSiape +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Joyce", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["siape", "dados_financeiros"], +) +def siape_dados_financeiros_dag() -> None: + """ + DAG que consome o endpoint consultaDadosFinanceiros da API SIAPE + e armazena os dados financeiros dos servidores no schema 'siape'. + """ + + @task + def fetch_and_store_dados_financeiros() -> None: + logging.info("Iniciando extração de dados financeiros por CPF") + cliente_siape = ClienteSiape() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + query = "SELECT DISTINCT cpf FROM siape.lista_servidores WHERE cpf IS NOT NULL" + cpfs = [row[0] for row in db.execute_query(query)] + logging.info(f"Total de CPFs encontrados: {len(cpfs)}") + + for cpf in cpfs: + try: + context = { + "siglaSistema": "PETRVS-IPEA", + "nomeSistema": "PDG-PETRVS-IPEA", + "senha": os.getenv("SIAPE_PASSWORD_USER"), + "cpf": cpf, + "codOrgao": "45206", + "parmExistPag": "b", + "parmTipoVinculo": "c", + } + + resposta_xml = cliente_siape.call( + "consultaDadosFinanceiros.xml.j2", context + ) + dados = ClienteSiape.parse_xml_to_dict(resposta_xml) + + if not dados: + logging.warning(f"Nenhum dado financeiro encontrado para CPF {cpf}") + continue + + db.alter_table( + data=dados, + table_name="dados_financeiros", + schema="siape", + ) + + db.insert_data( + [dados], + table_name="dados_financeiros", + conflict_fields=["cpf"], + primary_key=["cpf"], + schema="siape", + ) + + logging.info(f"Dado financeiro inserido para CPF {cpf}") + + except Exception as e: + logging.error(f"Erro ao processar CPF {cpf}: {e}") + continue + + fetch_and_store_dados_financeiros() + + +dag_instance = siape_dados_financeiros_dag() diff --git a/airflow_lappis/dags/data_ingest/dados_pa_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_pa_siape_ingest_dag.py new file mode 100644 index 00000000..1663200e --- /dev/null +++ b/airflow_lappis/dags/data_ingest/dados_pa_siape_ingest_dag.py @@ -0,0 +1,80 @@ +import os +import logging +from datetime import datetime, timedelta +from airflow.decorators import dag, task +from postgres_helpers import get_postgres_conn +from cliente_siape import ClienteSiape +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Joyce", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["siape", "dados_pa"], +) +def siape_dados_pa_dag() -> None: + """ + DAG que consome o endpoint consultaDadosPA da API SIAPE + e armazena o plano de atuação dos servidores no schema 'siape'. + """ + + @task + def fetch_and_store_dados_pa() -> None: + logging.info("Iniciando extração de dados de plano de atuação por CPF") + cliente_siape = ClienteSiape() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + query = "SELECT DISTINCT cpf FROM siape.lista_servidores WHERE cpf IS NOT NULL" + cpfs = [row[0] for row in db.execute_query(query)] + logging.info(f"Total de CPFs encontrados: {len(cpfs)}") + + for cpf in cpfs: + try: + context = { + "siglaSistema": "PETRVS-IPEA", + "nomeSistema": "PDG-PETRVS-IPEA", + "senha": os.getenv("SIAPE_PASSWORD_USER"), + "cpf": cpf, + "codOrgao": "45206", + "parmExistPag": "b", + "parmTipoVinculo": "c", + } + + resposta_xml = cliente_siape.call("consultaDadosPA.xml.j2", context) + dados = ClienteSiape.parse_xml_to_dict(resposta_xml) + + if not dados: + logging.warning(f"Nenhum dado PA encontrado para CPF {cpf}") + continue + + db.alter_table( + data=dados, + table_name="dados_pa", + schema="siape", + ) + + db.insert_data( + [dados], + table_name="dados_pa", + conflict_fields=["cpf"], + primary_key=["cpf"], + schema="siape", + ) + + logging.info(f"Plano de atuação inserido para CPF {cpf}") + + except Exception as e: + logging.error(f"Erro ao processar CPF {cpf}: {e}") + continue + + fetch_and_store_dados_pa() + + +dag_instance = siape_dados_pa_dag() diff --git a/airflow_lappis/dags/data_ingest/dados_uorg_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_uorg_siape_ingest_dag.py new file mode 100644 index 00000000..eab78988 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/dados_uorg_siape_ingest_dag.py @@ -0,0 +1,80 @@ +import os +import logging +from datetime import datetime, timedelta +from airflow.decorators import dag, task +from postgres_helpers import get_postgres_conn +from cliente_siape import ClienteSiape +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Joyce", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["siape", "dados_uorg"], +) +def siape_dados_uorg_dag() -> None: + """ + DAG que consome o endpoint consultaDadosUorg da API SIAPE + e armazena os dados da unidade organizacional do servidor no schema 'siape'. + """ + + @task + def fetch_and_store_dados_uorg() -> None: + logging.info("Iniciando extração de dados de UORG por CPF") + cliente_siape = ClienteSiape() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + query = "SELECT DISTINCT cpf FROM siape.lista_servidores WHERE cpf IS NOT NULL" + cpfs = [row[0] for row in db.execute_query(query)] + logging.info(f"Total de CPFs encontrados: {len(cpfs)}") + + for cpf in cpfs: + try: + context = { + "siglaSistema": "PETRVS-IPEA", + "nomeSistema": "PDG-PETRVS-IPEA", + "senha": os.getenv("SIAPE_PASSWORD_USER"), + "cpf": cpf, + "codOrgao": "45206", + "parmExistPag": "b", + "parmTipoVinculo": "c", + } + + resposta_xml = cliente_siape.call("consultaDadosUorg.xml.j2", context) + dados = ClienteSiape.parse_xml_to_dict(resposta_xml) + + if not dados: + logging.warning(f"Nenhum dado de UORG encontrado para CPF {cpf}") + continue + + db.alter_table( + data=dados, + table_name="dados_uorg", + schema="siape", + ) + + db.insert_data( + [dados], + table_name="dados_uorg", + conflict_fields=["cpf"], + primary_key=["cpf"], + schema="siape", + ) + + logging.info(f"Dado de UORG inserido para CPF {cpf}") + + except Exception as e: + logging.error(f"Erro ao processar CPF {cpf}: {e}") + continue + + fetch_and_store_dados_uorg() + + +dag_instance = siape_dados_uorg_dag() diff --git a/airflow_lappis/dags/data_ingest/lista_aposentadoria_parcial_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/lista_aposentadoria_parcial_siape_ingest_dag.py new file mode 100644 index 00000000..75d5360c --- /dev/null +++ b/airflow_lappis/dags/data_ingest/lista_aposentadoria_parcial_siape_ingest_dag.py @@ -0,0 +1,93 @@ +import os +import logging +from datetime import datetime, timedelta +from airflow.decorators import dag, task +from postgres_helpers import get_postgres_conn +from cliente_siape import ClienteSiape +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Joyce", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["siape", "lista_informacoes_aposentadoria_parcial"], +) +def siape_lista_info_aposentadoria_parcial_dag() -> None: + """ + DAG que consome o endpoint listaInformacoesAposentadoriaParcial da API SIAPE + e armazena os dados parciais de aposentadoria no schema 'siape'. + """ + + @task + def fetch_and_store_info_aposentadoria_parcial() -> None: + logging.info( + "Iniciando extração de informações parciais de aposentadoria por CPF" + ) + cliente_siape = ClienteSiape() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + query = "SELECT DISTINCT cpf FROM siape.lista_servidores WHERE cpf IS NOT NULL" + cpfs = [row[0] for row in db.execute_query(query)] + logging.info(f"Total de CPFs encontrados: {len(cpfs)}") + + for cpf in cpfs: + try: + context = { + "siglaSistema": "PETRVS-IPEA", + "nomeSistema": "PDG-PETRVS-IPEA", + "senha": os.getenv("SIAPE_PASSWORD_USER"), + "cpf": cpf, + "codOrgao": "45206", + "parmExistPag": "b", + "parmTipoVinculo": "c", + } + + resposta_xml = cliente_siape.call( + "listaInformacoesAposentadoriaParcial.xml.j2", context + ) + + dados = ClienteSiape.parse_xml_to_list( + xml_string=resposta_xml, + element_tag="ns2:InformacaoAposentadoriaParcial", + namespaces={ + "soapenv": "http://schemas.xmlsoap.org/soap/envelope/", + "ns1": "http://servico.wssiapenet", + "ns2": "http://entidade.wssiapenet", + }, + ) + + if not dados: + logging.warning(f"Nenhuma informação encontrada para CPF {cpf}") + continue + + db.alter_table( + data=dados[0], + table_name="info_aposentadoria_parcial", + schema="siape", + ) + + db.insert_data( + dados, + table_name="info_aposentadoria_parcial", + conflict_fields=None, + primary_key=None, + schema="siape", + ) + + logging.info(f"Dados parciais de aposentadoria inseridos para CPF {cpf}") + + except Exception as e: + logging.error(f"Erro ao processar CPF {cpf}: {e}") + continue + + fetch_and_store_info_aposentadoria_parcial() + + +dag_instance = siape_lista_info_aposentadoria_parcial_dag() diff --git a/airflow_lappis/dags/data_ingest/lista_aposentadoria_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/lista_aposentadoria_siape_ingest_dag.py new file mode 100644 index 00000000..1661d3bb --- /dev/null +++ b/airflow_lappis/dags/data_ingest/lista_aposentadoria_siape_ingest_dag.py @@ -0,0 +1,81 @@ +import os +import logging +from datetime import datetime, timedelta +from airflow.decorators import dag, task +from postgres_helpers import get_postgres_conn +from cliente_siape import ClienteSiape +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Joyce", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["siape", "aposentadoria"], +) +def siape_lista_info_aposentadoria_dag() -> None: + """ + DAG que consome o endpoint listaInformacoesAposentadoria da API SIAPE + e armazena os dados no schema 'siape'. + """ + + @task + def fetch_and_store_aposentadoria_info() -> None: + logging.info("Iniciando extração de informações de aposentadoria por CPF") + cliente_siape = ClienteSiape() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + query = "SELECT DISTINCT cpf FROM siape.lista_servidores WHERE cpf IS NOT NULL" + cpfs = [row[0] for row in db.execute_query(query)] + logging.info(f"Total de CPFs encontrados: {len(cpfs)}") + + for cpf in cpfs: + try: + context = { + "siglaSistema": "PETRVS-IPEA", + "nomeSistema": "PDG-PETRVS-IPEA", + "senha": os.getenv("SIAPE_PASSWORD_USER"), + "cpf": cpf, + "codOrgao": "45206", + "parmExistPag": "b", + "parmTipoVinculo": "c", + } + + resposta_xml = cliente_siape.call( + "listaInformacoesAposentadoria.xml.j2", context + ) + dados = ClienteSiape.parse_xml_to_dict(resposta_xml) + + if not dados: + logging.warning(f"Nenhum dado encontrado para CPF {cpf}") + continue + + db.alter_table( + data=dados, + table_name="info_aposentadoria", + schema="siape", + ) + db.insert_data( + [dados], + table_name="info_aposentadoria", + conflict_fields=["cpf"], + primary_key=["cpf"], + schema="siape", + ) + + logging.info(f"Dado de aposentadoria inserido para CPF {cpf}") + + except Exception as e: + logging.error(f"Erro ao processar CPF {cpf}: {e}") + continue + + fetch_and_store_aposentadoria_info() + + +dag_instance = siape_lista_info_aposentadoria_dag() From 7f01726d795eff752f0a033aef8d6f0ac9ca1d5b Mon Sep 17 00:00:00 2001 From: Joyce Date: Thu, 8 May 2025 08:19:25 -0300 Subject: [PATCH 079/317] fix(dag): corrige ingestao malformada --- .../lista_aposentadoria_siape_ingest_dag.py | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/lista_aposentadoria_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/lista_aposentadoria_siape_ingest_dag.py index 1661d3bb..f3cb6858 100644 --- a/airflow_lappis/dags/data_ingest/lista_aposentadoria_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/lista_aposentadoria_siape_ingest_dag.py @@ -1,4 +1,5 @@ import os +import time import logging from datetime import datetime, timedelta from airflow.decorators import dag, task @@ -31,20 +32,23 @@ def fetch_and_store_aposentadoria_info() -> None: postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) - query = "SELECT DISTINCT cpf FROM siape.lista_servidores WHERE cpf IS NOT NULL" - cpfs = [row[0] for row in db.execute_query(query)] - logging.info(f"Total de CPFs encontrados: {len(cpfs)}") + query = """ + SELECT DISTINCT cpf, matriculaSiape + FROM siape.lista_servidores + WHERE cpf IS NOT NULL AND matriculaSiape IS NOT NULL + """ + registros = db.execute_query(query) + logging.info(f"Total de registros encontrados: {len(registros)}") - for cpf in cpfs: + for cpf, matricula in registros: try: context = { "siglaSistema": "PETRVS-IPEA", "nomeSistema": "PDG-PETRVS-IPEA", "senha": os.getenv("SIAPE_PASSWORD_USER"), "cpf": cpf, - "codOrgao": "45206", - "parmExistPag": "b", - "parmTipoVinculo": "c", + "orgao": "45206", + "matricula": matricula, } resposta_xml = cliente_siape.call( @@ -75,6 +79,8 @@ def fetch_and_store_aposentadoria_info() -> None: logging.error(f"Erro ao processar CPF {cpf}: {e}") continue + time.sleep(0.1) + fetch_and_store_aposentadoria_info() From 0e190fde3e20d18632a727a0e155c3a79d76183f Mon Sep 17 00:00:00 2001 From: Joyce Date: Thu, 8 May 2025 08:45:25 -0300 Subject: [PATCH 080/317] fix(sql): corrige sql --- .../data_ingest/lista_aposentadoria_siape_ingest_dag.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/lista_aposentadoria_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/lista_aposentadoria_siape_ingest_dag.py index f3cb6858..a6d37eb5 100644 --- a/airflow_lappis/dags/data_ingest/lista_aposentadoria_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/lista_aposentadoria_siape_ingest_dag.py @@ -33,9 +33,9 @@ def fetch_and_store_aposentadoria_info() -> None: db = ClientPostgresDB(postgres_conn_str) query = """ - SELECT DISTINCT cpf, matriculaSiape - FROM siape.lista_servidores - WHERE cpf IS NOT NULL AND matriculaSiape IS NOT NULL + SELECT DISTINCT cpf, matriculasiape + FROM siape.dados_funcionais + WHERE cpf IS NOT NULL AND matriculasiape IS NOT NULL """ registros = db.execute_query(query) logging.info(f"Total de registros encontrados: {len(registros)}") From 7913294ac60f4db8d79772a191c3da14e7df2f29 Mon Sep 17 00:00:00 2001 From: Joyce Dionizio Date: Thu, 8 May 2025 12:39:34 +0000 Subject: [PATCH 081/317] Fix/dags ingest --- .../dados_afastamento_historico_siape_ingest_dag.py | 5 +++++ .../templates/siape/consultaDadosAfastamentoHistorico.xml.j2 | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/airflow_lappis/dags/data_ingest/dados_afastamento_historico_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_afastamento_historico_siape_ingest_dag.py index afea1a21..6092819e 100644 --- a/airflow_lappis/dags/data_ingest/dados_afastamento_historico_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_afastamento_historico_siape_ingest_dag.py @@ -51,11 +51,16 @@ def fetch_and_store_afastamento_historico() -> None: "codOrgao": "45206", "parmExistPag": "b", "parmTipoVinculo": "c", + "anoInicial": "2024", + "mesInicial": "01", + "anoFinal": "2025", + "mesFinal": "12", } resposta_xml = cliente_siape.call( "consultaDadosAfastamentoHistorico.xml.j2", context ) + dados = ClienteSiape.parse_xml_to_list( xml_string=resposta_xml, element_tag="ns2:AfastamentoHistorico", diff --git a/airflow_lappis/templates/siape/consultaDadosAfastamentoHistorico.xml.j2 b/airflow_lappis/templates/siape/consultaDadosAfastamentoHistorico.xml.j2 index 887b3203..2552e80f 100644 --- a/airflow_lappis/templates/siape/consultaDadosAfastamentoHistorico.xml.j2 +++ b/airflow_lappis/templates/siape/consultaDadosAfastamentoHistorico.xml.j2 @@ -16,4 +16,4 @@ {{ mesFinal }} - + \ No newline at end of file From c0aa67dd7a25437222db0a10e0b56d14a9625439 Mon Sep 17 00:00:00 2001 From: Joyce Dionizio Date: Thu, 8 May 2025 13:06:35 +0000 Subject: [PATCH 082/317] Fix/dags ingest --- ..._afastamento_historico_siape_ingest_dag.py | 31 ++++++++++--------- .../dados_afastamento_siape_ingest_dag.py | 29 +++++++++-------- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/dados_afastamento_historico_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_afastamento_historico_siape_ingest_dag.py index 6092819e..407be5ce 100644 --- a/airflow_lappis/dags/data_ingest/dados_afastamento_historico_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_afastamento_historico_siape_ingest_dag.py @@ -74,21 +74,22 @@ def fetch_and_store_afastamento_historico() -> None: for row in dados: row["cpf"] = cpf - db.alter_table( - data=dados[0], - table_name="afastamento_historico", - schema="siape", - ) - - db.insert_data( - dados, - table_name="afastamento_historico", - conflict_fields=None, - primary_key=None, - schema="siape", - ) - - logging.info(f"{len(dados)} registros inseridos para CPF {cpf}") + if dados: + db.alter_table( + data=dados[0], + table_name="afastamento_historico", + schema="siape", + ) + + db.insert_data( + dados, + table_name="afastamento_historico", + conflict_fields=None, + primary_key=None, + schema="siape", + ) + + logging.info(f"{len(dados)} registros inseridos para CPF {cpf}") except Exception as e: logging.error(f"Erro ao processar CPF {cpf}: {e}") diff --git a/airflow_lappis/dags/data_ingest/dados_afastamento_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_afastamento_siape_ingest_dag.py index bd0d1bc1..1520bbc5 100644 --- a/airflow_lappis/dags/data_ingest/dados_afastamento_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_afastamento_siape_ingest_dag.py @@ -56,21 +56,24 @@ def fetch_and_store_dados_afastamento() -> None: logging.warning(f"Nenhum dado de afastamento para CPF {cpf}") continue - db.alter_table( - data=dados, - table_name="dados_afastamento", - schema="siape", - ) + dados["cpf"] = cpf - db.insert_data( - [dados], - table_name="dados_afastamento", - conflict_fields=["cpf"], - primary_key=["cpf"], - schema="siape", - ) + if dados: + db.alter_table( + data=dados, + table_name="dados_afastamento", + schema="siape", + ) + + db.insert_data( + [dados], + table_name="dados_afastamento", + conflict_fields=["cpf"], + primary_key=["cpf"], + schema="siape", + ) - logging.info(f"Dado de afastamento inserido para CPF {cpf}") + logging.info(f"Dado de afastamento inserido para CPF {cpf}") except Exception as e: logging.error(f"Erro ao processar CPF {cpf}: {e}") From 886ff34ffa63b498d9a00455fd7738544d5bc605 Mon Sep 17 00:00:00 2001 From: Joyce Date: Thu, 8 May 2025 10:26:53 -0300 Subject: [PATCH 083/317] fix(dags): insere coluna cpf nas tabelas --- .../dags/data_ingest/dados_escolares_siape_ingest_dag.py | 2 ++ airflow_lappis/dags/data_ingest/dados_financeiros_siape_dag.py | 2 ++ airflow_lappis/dags/data_ingest/dados_pa_siape_ingest_dag.py | 2 ++ airflow_lappis/dags/data_ingest/dados_uorg_siape_ingest_dag.py | 2 ++ 4 files changed, 8 insertions(+) diff --git a/airflow_lappis/dags/data_ingest/dados_escolares_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_escolares_siape_ingest_dag.py index bef240e6..760be974 100644 --- a/airflow_lappis/dags/data_ingest/dados_escolares_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_escolares_siape_ingest_dag.py @@ -56,6 +56,8 @@ def fetch_and_store_dados_escolares() -> None: logging.warning(f"Nenhum dado escolar encontrado para CPF {cpf}") continue + dados["cpf"] = cpf + db.alter_table( data=dados, table_name="dados_escolares", diff --git a/airflow_lappis/dags/data_ingest/dados_financeiros_siape_dag.py b/airflow_lappis/dags/data_ingest/dados_financeiros_siape_dag.py index d980a5de..69bf521e 100644 --- a/airflow_lappis/dags/data_ingest/dados_financeiros_siape_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_financeiros_siape_dag.py @@ -56,6 +56,8 @@ def fetch_and_store_dados_financeiros() -> None: logging.warning(f"Nenhum dado financeiro encontrado para CPF {cpf}") continue + dados["cpf"] = cpf + db.alter_table( data=dados, table_name="dados_financeiros", diff --git a/airflow_lappis/dags/data_ingest/dados_pa_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_pa_siape_ingest_dag.py index 1663200e..443eefcd 100644 --- a/airflow_lappis/dags/data_ingest/dados_pa_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_pa_siape_ingest_dag.py @@ -54,6 +54,8 @@ def fetch_and_store_dados_pa() -> None: logging.warning(f"Nenhum dado PA encontrado para CPF {cpf}") continue + dados["cpf"] = cpf + db.alter_table( data=dados, table_name="dados_pa", diff --git a/airflow_lappis/dags/data_ingest/dados_uorg_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_uorg_siape_ingest_dag.py index eab78988..79d39f2d 100644 --- a/airflow_lappis/dags/data_ingest/dados_uorg_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_uorg_siape_ingest_dag.py @@ -54,6 +54,8 @@ def fetch_and_store_dados_uorg() -> None: logging.warning(f"Nenhum dado de UORG encontrado para CPF {cpf}") continue + dados["cpf"] = cpf + db.alter_table( data=dados, table_name="dados_uorg", From 1fc09667fbd44521fb12e9bf7a2950c64fa24ee5 Mon Sep 17 00:00:00 2001 From: Joyce Dionizio Date: Thu, 8 May 2025 15:01:24 +0000 Subject: [PATCH 084/317] Fix/afastamento dependentes --- ..._afastamento_historico_siape_ingest_dag.py | 12 +-- .../dados_dependentes_siape_ingest_dag.py | 18 ++-- ..._aposentadoria_parcial_siape_ingest_dag.py | 93 ------------------- airflow_lappis/plugins/cliente_siape.py | 88 +++++++++++++++++- ...istaInformacoesAposentadoriaParcial.xml.j2 | 16 ---- 5 files changed, 90 insertions(+), 137 deletions(-) delete mode 100644 airflow_lappis/dags/data_ingest/lista_aposentadoria_parcial_siape_ingest_dag.py delete mode 100644 airflow_lappis/templates/siape/listaInformacoesAposentadoriaParcial.xml.j2 diff --git a/airflow_lappis/dags/data_ingest/dados_afastamento_historico_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_afastamento_historico_siape_ingest_dag.py index 407be5ce..7d728b3a 100644 --- a/airflow_lappis/dags/data_ingest/dados_afastamento_historico_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_afastamento_historico_siape_ingest_dag.py @@ -35,12 +35,6 @@ def fetch_and_store_afastamento_historico() -> None: cpfs = [row[0] for row in db.execute_query(query)] logging.info(f"Total de CPFs encontrados: {len(cpfs)}") - ns = { - "soapenv": "http://schemas.xmlsoap.org/soap/envelope/", - "ns1": "http://servico.wssiapenet", - "ns2": "http://entidade.wssiapenet", - } - for cpf in cpfs: try: context = { @@ -61,11 +55,7 @@ def fetch_and_store_afastamento_historico() -> None: "consultaDadosAfastamentoHistorico.xml.j2", context ) - dados = ClienteSiape.parse_xml_to_list( - xml_string=resposta_xml, - element_tag="ns2:AfastamentoHistorico", - namespaces=ns, - ) + dados = ClienteSiape.parse_afastamento_historico(resposta_xml) if not dados: logging.info(f"Nenhum dado de afastamento histórico para CPF {cpf}") diff --git a/airflow_lappis/dags/data_ingest/dados_dependentes_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_dependentes_siape_ingest_dag.py index ceb192b6..e6ea0668 100644 --- a/airflow_lappis/dags/data_ingest/dados_dependentes_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_dependentes_siape_ingest_dag.py @@ -35,11 +35,6 @@ def fetch_and_store_dados_dependentes() -> None: cpfs = [row[0] for row in db.execute_query(query)] logging.info(f"Total de CPFs encontrados: {len(cpfs)}") - ns = { - "soapenv": "http://schemas.xmlsoap.org/soap/envelope/", - "ns2": "http://entidade.wssiapenet", - } - for cpf in cpfs: try: context = { @@ -55,16 +50,15 @@ def fetch_and_store_dados_dependentes() -> None: resposta_xml = cliente_siape.call( "consultaDadosDependentes.xml.j2", context ) - dados = ClienteSiape.parse_xml_to_list( - xml_string=resposta_xml, - element_tag="ns2:Dependente", - namespaces=ns, - ) + dados = ClienteSiape.parse_dependentes(resposta_xml) if not dados: logging.warning(f"Nenhum dependente encontrado para CPF {cpf}") continue + for row in dados: + row["cpf"] = cpf + db.alter_table( data=dados[0], table_name="dados_dependentes", @@ -74,8 +68,8 @@ def fetch_and_store_dados_dependentes() -> None: db.insert_data( dados, table_name="dados_dependentes", - conflict_fields=["cpf", "nomeDependente"], - primary_key=["cpf", "nomeDependente"], + conflict_fields=["cpf", "nome"], + primary_key=["cpf", "nome"], schema="siape", ) diff --git a/airflow_lappis/dags/data_ingest/lista_aposentadoria_parcial_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/lista_aposentadoria_parcial_siape_ingest_dag.py deleted file mode 100644 index 75d5360c..00000000 --- a/airflow_lappis/dags/data_ingest/lista_aposentadoria_parcial_siape_ingest_dag.py +++ /dev/null @@ -1,93 +0,0 @@ -import os -import logging -from datetime import datetime, timedelta -from airflow.decorators import dag, task -from postgres_helpers import get_postgres_conn -from cliente_siape import ClienteSiape -from cliente_postgres import ClientPostgresDB - - -@dag( - schedule_interval="@daily", - start_date=datetime(2023, 1, 1), - catchup=False, - default_args={ - "owner": "Joyce", - "retries": 1, - "retry_delay": timedelta(minutes=5), - }, - tags=["siape", "lista_informacoes_aposentadoria_parcial"], -) -def siape_lista_info_aposentadoria_parcial_dag() -> None: - """ - DAG que consome o endpoint listaInformacoesAposentadoriaParcial da API SIAPE - e armazena os dados parciais de aposentadoria no schema 'siape'. - """ - - @task - def fetch_and_store_info_aposentadoria_parcial() -> None: - logging.info( - "Iniciando extração de informações parciais de aposentadoria por CPF" - ) - cliente_siape = ClienteSiape() - postgres_conn_str = get_postgres_conn() - db = ClientPostgresDB(postgres_conn_str) - - query = "SELECT DISTINCT cpf FROM siape.lista_servidores WHERE cpf IS NOT NULL" - cpfs = [row[0] for row in db.execute_query(query)] - logging.info(f"Total de CPFs encontrados: {len(cpfs)}") - - for cpf in cpfs: - try: - context = { - "siglaSistema": "PETRVS-IPEA", - "nomeSistema": "PDG-PETRVS-IPEA", - "senha": os.getenv("SIAPE_PASSWORD_USER"), - "cpf": cpf, - "codOrgao": "45206", - "parmExistPag": "b", - "parmTipoVinculo": "c", - } - - resposta_xml = cliente_siape.call( - "listaInformacoesAposentadoriaParcial.xml.j2", context - ) - - dados = ClienteSiape.parse_xml_to_list( - xml_string=resposta_xml, - element_tag="ns2:InformacaoAposentadoriaParcial", - namespaces={ - "soapenv": "http://schemas.xmlsoap.org/soap/envelope/", - "ns1": "http://servico.wssiapenet", - "ns2": "http://entidade.wssiapenet", - }, - ) - - if not dados: - logging.warning(f"Nenhuma informação encontrada para CPF {cpf}") - continue - - db.alter_table( - data=dados[0], - table_name="info_aposentadoria_parcial", - schema="siape", - ) - - db.insert_data( - dados, - table_name="info_aposentadoria_parcial", - conflict_fields=None, - primary_key=None, - schema="siape", - ) - - logging.info(f"Dados parciais de aposentadoria inseridos para CPF {cpf}") - - except Exception as e: - logging.error(f"Erro ao processar CPF {cpf}: {e}") - continue - - fetch_and_store_info_aposentadoria_parcial() - - -dag_instance = siape_lista_info_aposentadoria_parcial_dag() diff --git a/airflow_lappis/plugins/cliente_siape.py b/airflow_lappis/plugins/cliente_siape.py index 124fe2a7..bcd8642f 100755 --- a/airflow_lappis/plugins/cliente_siape.py +++ b/airflow_lappis/plugins/cliente_siape.py @@ -149,16 +149,15 @@ def parse_xml_to_list( xml_string: str, element_tag: str, namespaces: Dict[str, str] ) -> list[dict[str, str | None]]: """ - Parse a SOAP XML response and return a list of dictionaries, - one per repeated element. + Generic parser for repeating XML elements (like lista servidores). Args: xml_string (str): SOAP XML response. - element_tag (str): Tag do elemento que se repete (ex: 'ns2:Uorg'). - namespaces (Dict[str, str]): Mapeamento de namespaces. + element_tag (str): Tag do elemento que se repete. + namespaces (Dict[str, str]): XML namespaces. Returns: - list[dict[str, str]]: Lista de registros extraídos. + list[dict[str, str | None]]: Lista de registros. """ root = ET.fromstring(xml_string) body = root.find("soapenv:Body", namespaces) @@ -177,3 +176,82 @@ def parse_xml_to_list( resultado.append(row) return resultado + + @staticmethod + def parse_afastamento_historico(xml_string: str) -> list[dict[str, Any]]: + """ + Custom parser for afastamento histórico: extrai DadosFerias e DadosOcorrencias. + + Args: + xml_string (str): SOAP XML response. + + Returns: + list[dict[str, str | None]]: Lista de registros combinando + férias e ocorrências. + """ + ns = { + "soapenv": "http://schemas.xmlsoap.org/soap/envelope/", + "ns2": "http://tipo.servico.wssiapenet", + } + root = ET.fromstring(xml_string) + body = root.find("soapenv:Body", ns) + if body is None: + return [] + + dados = [] + for item in body.findall(".//ns2:DadosFerias", ns): + registro = {} + for elem in item: + tag = elem.tag.split("}")[-1] + registro[tag] = elem.text.strip() if elem.text else None + dados.append(registro) + + for item in body.findall(".//ns2:DadosOcorrencias", ns): + registro = {} + for elem in item: + tag = elem.tag.split("}")[-1] + registro[tag] = elem.text.strip() if elem.text else None + dados.append(registro) + + return dados + + @staticmethod + def parse_dependentes(xml_string: str) -> list[dict[str, Any]]: + """ + Custom parser para consultaDadosDependentes: extrai dados do + dependente e seus benefícios. + + Args: + xml_string (str): SOAP XML response. + + Returns: + list[dict[str, Any]]: Lista de dependentes com campo + `arrayBeneficios` como sublista. + """ + ns = { + "soapenv": "http://schemas.xmlsoap.org/soap/envelope/", + "ns2": "http://tipo.servico.wssiapenet", + } + root = ET.fromstring(xml_string) + body = root.find("soapenv:Body", ns) + if body is None: + return [] + + dependentes = [] + for item in body.findall(".//ns2:DadosDependentes", ns): + registro: dict[str, Any] = {} + for elem in item: + tag = elem.tag.split("}")[-1] + if tag == "arrayBeneficios": + beneficios = [] + for b in elem: + beneficio = { + e.tag.split("}")[-1]: e.text.strip() for e in b if e.text + } + beneficios.append(beneficio) + registro[tag] = beneficios + else: + registro[tag] = elem.text.strip() if elem.text else None + dependentes.append(registro) + + return dependentes diff --git a/airflow_lappis/templates/siape/listaInformacoesAposentadoriaParcial.xml.j2 b/airflow_lappis/templates/siape/listaInformacoesAposentadoriaParcial.xml.j2 deleted file mode 100644 index e452685a..00000000 --- a/airflow_lappis/templates/siape/listaInformacoesAposentadoriaParcial.xml.j2 +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - {{ siglaSistema }} - {{ nomeSistema }} - {{ senha }} - {{ cpf }} - {{ anoMes }} - {{ codOrgao }} - {{ nivelSituacaoFuncional }} - {{ matricula }} - - - From afd6b66a62d8e50311be28c17f3ef0efc5f4728d Mon Sep 17 00:00:00 2001 From: VictorSzk Date: Thu, 8 May 2025 15:06:05 -0300 Subject: [PATCH 085/317] Adding columns --- .../ted_dbt/gold/ted_resumo_orcamentario.sql | 14 +++++++------- .../ipea/models/ted_dbt/silver/nc_plano_acao.sql | 2 ++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/ted_resumo_orcamentario.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/ted_resumo_orcamentario.sql index 75ae00d5..4a261167 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/ted_resumo_orcamentario.sql +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/ted_resumo_orcamentario.sql @@ -4,7 +4,7 @@ with -- Valor firmado valor_firmado_tb as ( - select id_plano_acao, vl_total_plano_acao as valor_firmado_tb + select id_plano_acao as plano_acao, vl_total_plano_acao as valor_firmado_tb from {{ ref("planos_acao") }} ), @@ -12,7 +12,7 @@ with -- Orçamento devolvido valores_orcamentos_tb as ( select - plano_acao as id_plano_acao, + plano_acao, sum( case when nc_evento not in ('300301', '300307') then nc_valor else 0 end ) as orcamento_recebido, @@ -30,7 +30,7 @@ with -- Utilizado/pago valores_empenhados_tb as ( select - plano_acao as id_plano_acao, + plano_acao, sum( case when despesas_empenhadas > 0 then despesas_empenhadas else 0 end ) as empenhado, @@ -49,7 +49,7 @@ with -- Utilizado/pago valores_financeiro_tb as ( select - plano_acao as id_plano_acao, + plano_acao, sum( case when pf_acao = 'TRANSFERENCIA' then pf_valor_linha else 0 end ) as financeiro_recebido, @@ -67,7 +67,7 @@ with -- Final select * from valor_firmado_tb -left join valores_orcamentos_tb using (id_plano_acao) -left join valores_empenhados_tb using (id_plano_acao) -left join valores_financeiro_tb using (id_plano_acao) +left join valores_orcamentos_tb using (plano_acao) +left join valores_empenhados_tb using (plano_acao) +left join valores_financeiro_tb using (plano_acao) where id_plano_acao is not null diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/nc_plano_acao.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/nc_plano_acao.sql index fe4f1c7f..05db3dd1 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/nc_plano_acao.sql +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/nc_plano_acao.sql @@ -11,10 +11,12 @@ with result_table as ( select pda.plano_acao, + emissao_dia, nc_transferencia, right(nc, 12) as nc, nc_fonte_recursos, ptres, + left(nc, 6) as ug_emitente, nc_natureza_despesa, nc_evento, nc_evento_descr, From 8b7b299f83bea7f464a34dff873d325becdf0fdc Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Sat, 10 May 2025 20:28:31 -0300 Subject: [PATCH 086/317] feat(tags): ajusta e adiciona tags --- .../dags/data_ingest/contratos_inativos_ingest_dag.py | 2 +- airflow_lappis/dags/data_ingest/contratos_ingest_dag.py | 2 +- airflow_lappis/dags/data_ingest/cronograma_ingest_dag.py | 2 +- airflow_lappis/dags/data_ingest/empenhos_ingest_dag.py | 2 +- airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py | 1 + airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py | 3 +-- airflow_lappis/dags/data_ingest/faturas_ingest_dag.py | 2 +- airflow_lappis/dags/data_ingest/nc_tesouro_ingest.dag.py | 1 + airflow_lappis/dags/data_ingest/pf_tesouro_ingest_dag.py | 1 + .../dags/data_ingest/unidade_organizacional_ingest_dag.py | 2 +- 10 files changed, 10 insertions(+), 8 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py b/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py index c940f31b..f0452797 100755 --- a/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py @@ -17,7 +17,7 @@ "retries": 1, "retry_delay": timedelta(minutes=5), }, - tags=["contratos_inativos_api"], + tags=["contratos_inativos_api", "compras_gov"], ) def api_contratos_inativos_dag() -> None: """DAG para buscar e armazenar contratos inativos de uma API no PostgreSQL.""" diff --git a/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py b/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py index 2b00db1a..00f25e90 100755 --- a/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py @@ -18,7 +18,7 @@ "retries": 1, "retry_delay": timedelta(minutes=5), }, - tags=["contratos_api"], + tags=["contratos_api", "compras_gov"], ) def api_contratos_dag() -> None: """DAG para buscar e armazenar contratos por órgão definido.""" diff --git a/airflow_lappis/dags/data_ingest/cronograma_ingest_dag.py b/airflow_lappis/dags/data_ingest/cronograma_ingest_dag.py index ca6e2842..c6d264f9 100755 --- a/airflow_lappis/dags/data_ingest/cronograma_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/cronograma_ingest_dag.py @@ -14,7 +14,7 @@ "retries": 1, "retry_delay": timedelta(minutes=5), }, - tags=["cronogramas_api"], + tags=["cronogramas_api", "compras_gov"], ) def api_cronogramas_dag() -> None: """DAG para buscar e armazenar cronogramas de uma API no PostgreSQL.""" diff --git a/airflow_lappis/dags/data_ingest/empenhos_ingest_dag.py b/airflow_lappis/dags/data_ingest/empenhos_ingest_dag.py index 845dafd3..63495ec9 100755 --- a/airflow_lappis/dags/data_ingest/empenhos_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/empenhos_ingest_dag.py @@ -15,7 +15,7 @@ "retries": 1, "retry_delay": timedelta(minutes=5), }, - tags=["empenhos_api"], + tags=["empenhos_api", "compras_gov"], ) def api_empenhos_dag() -> None: """DAG para buscar e armazenar empenhos de uma API no PostgreSQL.""" diff --git a/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py index e10ff76d..f6e3a638 100755 --- a/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py @@ -53,6 +53,7 @@ schedule_interval="0 13 * * 1-6", start_date=datetime(2023, 12, 1), catchup=False, + tags=["email", "empenhos", "tesouro"], ) as dag: def process_email_data(**context: Dict[str, Any]) -> Optional[str]: diff --git a/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py index 24c40005..1a068f96 100755 --- a/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py @@ -1,12 +1,10 @@ from typing import Optional, Dict, Any - from airflow import DAG from airflow.operators.python import PythonOperator from airflow.models import Variable from datetime import datetime, timedelta import logging import json - from postgres_helpers import get_postgres_conn from cliente_email import fetch_and_process_email from cliente_postgres import ClientPostgresDB @@ -54,6 +52,7 @@ schedule_interval="0 13 * * 1-6", start_date=datetime(2023, 12, 1), catchup=False, + tags=["email", "estagios", "tesouro"], ) as dag: def process_email_data(**context: Dict[str, Any]) -> Optional[str]: diff --git a/airflow_lappis/dags/data_ingest/faturas_ingest_dag.py b/airflow_lappis/dags/data_ingest/faturas_ingest_dag.py index f3d2526c..5c8d8d91 100755 --- a/airflow_lappis/dags/data_ingest/faturas_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/faturas_ingest_dag.py @@ -14,7 +14,7 @@ "retries": 1, "retry_delay": timedelta(minutes=5), }, - tags=["faturas_api"], + tags=["faturas_api", "compras_gov"], ) def api_faturas_dag() -> None: """DAG para buscar e armazenar faturas de uma API no PostgreSQL.""" diff --git a/airflow_lappis/dags/data_ingest/nc_tesouro_ingest.dag.py b/airflow_lappis/dags/data_ingest/nc_tesouro_ingest.dag.py index 108822c7..0ef6ef64 100644 --- a/airflow_lappis/dags/data_ingest/nc_tesouro_ingest.dag.py +++ b/airflow_lappis/dags/data_ingest/nc_tesouro_ingest.dag.py @@ -55,6 +55,7 @@ schedule_interval="0 13 * * 1-6", start_date=datetime(2023, 12, 1), catchup=False, + tags=["email", "ncs", "tesouro"], ) as dag: def process_email_data_enviadas(**context: Dict[str, Any]) -> Optional[List[Dict]]: diff --git a/airflow_lappis/dags/data_ingest/pf_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/pf_tesouro_ingest_dag.py index 3b86153c..508d5103 100644 --- a/airflow_lappis/dags/data_ingest/pf_tesouro_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/pf_tesouro_ingest_dag.py @@ -56,6 +56,7 @@ schedule_interval="0 13 * * 1-6", start_date=datetime(2023, 12, 1), catchup=False, + tags=["email", "pfs", "tesouro"], ) as dag: def process_email_data_enviadas(**context: Dict[str, Any]) -> Optional[List[Dict]]: diff --git a/airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py b/airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py index f609a658..498c337b 100755 --- a/airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py @@ -14,7 +14,7 @@ "retries": 1, "retry_delay": timedelta(minutes=5), }, - tags=["estrutura_organizacional"], + tags=["estrutura_organizacional", "siorg"], ) def api_unidade_organizacional_dag() -> None: """DAG para buscar e armazenar dados da Estrutura Organizacional From a770fb1330ef8c1200cf39cf550dc925be1c4721 Mon Sep 17 00:00:00 2001 From: Joyce Date: Mon, 12 May 2025 07:26:59 -0300 Subject: [PATCH 087/317] feat(cliente): normaliza dados dos dependentes --- airflow_lappis/plugins/cliente_siape.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/airflow_lappis/plugins/cliente_siape.py b/airflow_lappis/plugins/cliente_siape.py index bcd8642f..cf1abc55 100755 --- a/airflow_lappis/plugins/cliente_siape.py +++ b/airflow_lappis/plugins/cliente_siape.py @@ -225,8 +225,7 @@ def parse_dependentes(xml_string: str) -> list[dict[str, Any]]: xml_string (str): SOAP XML response. Returns: - list[dict[str, Any]]: Lista de dependentes com campo - `arrayBeneficios` como sublista. + list[dict[str, Any]]: Lista de registros normalizados de dependentes. """ ns = { "soapenv": "http://schemas.xmlsoap.org/soap/envelope/", @@ -237,21 +236,28 @@ def parse_dependentes(xml_string: str) -> list[dict[str, Any]]: if body is None: return [] - dependentes = [] + resultado = [] for item in body.findall(".//ns2:DadosDependentes", ns): - registro: dict[str, Any] = {} + base_info: dict[str, Any] = {} + beneficios: list[dict[str, str]] = [] + for elem in item: tag = elem.tag.split("}")[-1] if tag == "arrayBeneficios": - beneficios = [] for b in elem: beneficio = { e.tag.split("}")[-1]: e.text.strip() for e in b if e.text } beneficios.append(beneficio) - registro[tag] = beneficios else: - registro[tag] = elem.text.strip() if elem.text else None - dependentes.append(registro) + base_info[tag] = elem.text.strip() if elem.text else None - return dependentes + if not beneficios: + resultado.append(base_info) + else: + for beneficio in beneficios: + row = base_info.copy() + row.update(beneficio) + resultado.append(row) + + return resultado From 0bab77619200b59b9fd32480a06e7e9137ea9d2b Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Mon, 12 May 2025 14:19:05 +0000 Subject: [PATCH 088/317] feat(param): adiciona funcao param para passar ano de backfill --- .../nota_empenho_siafi_ingest_dag.py | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/nota_empenho_siafi_ingest_dag.py b/airflow_lappis/dags/data_ingest/nota_empenho_siafi_ingest_dag.py index bdf77996..a929976b 100644 --- a/airflow_lappis/dags/data_ingest/nota_empenho_siafi_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/nota_empenho_siafi_ingest_dag.py @@ -2,7 +2,9 @@ import yaml from airflow.decorators import dag, task from airflow.models import Variable +from airflow.models.param import Param from datetime import datetime, timedelta +from typing import Dict, Any from cliente_siafi import ClienteSiafi from cliente_postgres import ClientPostgresDB from postgres_helpers import get_postgres_conn @@ -17,11 +19,25 @@ "retries": 1, "retry_delay": timedelta(minutes=5), }, + params={ + "ano_inicio": Param( + default=None, + type=["integer", "null"], + title="Ano de Início", + description="Backfill: Ano inicio para busca de notas de empenho. (type=int)", + ), + "ano_fim": Param( + default=None, + type=["integer", "null"], + title="Ano de Fim", + description="Backfill: Ano final para busca de notas de empenho. (type=int)", + ), + }, tags=["nota_empenho", "siafi_api"], ) def nota_empenho_siafi_ingest_dag() -> None: @task - def fetch_and_store_notas_empenho() -> None: + def fetch_and_store_notas_empenho(**context: Dict[str, Any]) -> None: logging.info("Iniciando fetch_and_store_notas_empenho") orgao_alvo = Variable.get("airflow_orgao", default_var=None) @@ -41,10 +57,19 @@ def fetch_and_store_notas_empenho() -> None: cliente = ClienteSiafi() postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) + + params = context["params"] + ano_inicio = params.get("ano_inicio") + ano_fim = params.get("ano_fim") + ano_atual = datetime.now().year + ano_inicio = ano_inicio or ano_atual + ano_fim = ano_fim or ano_atual + + anos_consulta = list(range(ano_inicio, ano_fim + 1)) for ug in ugs_emitentes: - for ano in range(2023, ano_atual + 1): + for ano in anos_consulta: num_empenho = 1 while True: num_empenho_str = str(num_empenho).zfill(6) From 9a8e5d20a391e153016890796c77f0a548964feb Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Mon, 12 May 2025 15:49:12 +0000 Subject: [PATCH 089/317] feat(pf): adiciona pf-descricao --- .../dags/data_ingest/drop_tables_dag.py | 42 ++++++++++++------- .../dags/data_ingest/pf_tesouro_ingest_dag.py | 23 +++++----- .../ipea/models/ted_dbt/bronze/pf_tesouro.sql | 1 + 3 files changed, 40 insertions(+), 26 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/drop_tables_dag.py b/airflow_lappis/dags/data_ingest/drop_tables_dag.py index 91f689bf..05751e20 100644 --- a/airflow_lappis/dags/data_ingest/drop_tables_dag.py +++ b/airflow_lappis/dags/data_ingest/drop_tables_dag.py @@ -1,8 +1,10 @@ import logging from airflow.decorators import dag, task from datetime import datetime, timedelta +from airflow.models import Param from postgres_helpers import get_postgres_conn from cliente_postgres import ClientPostgresDB +from typing import Dict @dag( @@ -15,29 +17,39 @@ "retry_delay": timedelta(minutes=5), }, tags=["siape", "admin", "drop"], + params={ + "tabela": Param("", description="Nome da tabela a ser dropada"), + "schema": Param("siape", description="Schema onde está a tabela"), + }, ) -def drop_siape_tabelas_dag() -> None: +def drop_tabela_parametrizada_dag() -> None: """ - DAG para remover tabelas antigas do schema siape. - Útil para limpar estrutura antes de reingestão. + DAG para remover uma tabela específica de um schema no Postgres. + Tabela e schema devem ser informados via parâmetros 'tabela' e 'schema'. """ @task - def drop_tabelas() -> None: - logging.info("Iniciando remoção de tabelas antigas do schema siape") + def drop_tabela(params: Dict[str, str]) -> None: + tabela = params.get("tabela") + schema = params.get("schema") + + if not tabela: + raise ValueError("Parâmetro 'tabela' não informado.") + if not schema: + raise ValueError("Parâmetro 'schema' não informado.") + + logging.info(f"Iniciando remoção da tabela {schema}.{tabela}") conn_str = get_postgres_conn() db = ClientPostgresDB(conn_str) - tabelas = ["dados_funcionais", "dados_funcionais_siape"] - - for tabela in tabelas: - try: - db.drop_table_if_exists(tabela, schema="siape") - logging.info(f"Tabela {tabela} removida com sucesso.") - except Exception as e: - logging.error(f"Erro ao remover tabela {tabela}: {e}") + try: + db.drop_table_if_exists(tabela, schema=schema) + logging.info(f"Tabela {schema}.{tabela} removida com sucesso.") + except Exception as e: + logging.error(f"Erro ao remover tabela {schema}.{tabela}: {e}") + raise - drop_tabelas() + drop_tabela(drop_tabela_parametrizada_dag.params) -dag_instance = drop_siape_tabelas_dag() +dag_instance = drop_tabela_parametrizada_dag() diff --git a/airflow_lappis/dags/data_ingest/pf_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/pf_tesouro_ingest_dag.py index 508d5103..11ebe4e8 100644 --- a/airflow_lappis/dags/data_ingest/pf_tesouro_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/pf_tesouro_ingest_dag.py @@ -30,17 +30,18 @@ 6: "pf_evento", 7: "pf_evento_descricao", 8: "pf", - 9: "pf_acao", - 10: "pf_acao_descricao", - 11: "pf_fonte_recursos", - 12: "pf_fonte_recursos_descricao", - 13: "pf_vinculacao_pagamento", - 14: "pf_vinculacao_pagamento_descricao", - 15: "pf_categoria_gasto", - 16: "pf_recurso", - 17: "pf_recurso_descricao", - 18: "doc_observacao", - 19: "pf_valor_linha", + 9: "pf_inscricao", + 10: "pf_acao", + 11: "pf_acao_descricao", + 12: "pf_fonte_recursos", + 13: "pf_fonte_recursos_descricao", + 14: "pf_vinculacao_pagamento", + 15: "pf_vinculacao_pagamento_descricao", + 16: "pf_categoria_gasto", + 17: "pf_recurso", + 18: "pf_recurso_descricao", + 19: "doc_observacao", + 20: "pf_valor_linha", } # Assuntos dos emails a serem processados diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/pf_tesouro.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/pf_tesouro.sql index 90e5e811..26b3b9a3 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/pf_tesouro.sql +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/pf_tesouro.sql @@ -11,6 +11,7 @@ with pf_evento, pf_evento_descricao, right(pf, 12) as pf, + pf_inscricao, pf_acao, pf_acao_descricao, pf_fonte_recursos, From 5d07400cd80c33150646646794a2c9e1c25ef117 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Mon, 12 May 2025 13:39:20 -0300 Subject: [PATCH 090/317] fix(drop): ajusta dag de drop --- airflow_lappis/dags/data_ingest/drop_tables_dag.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/drop_tables_dag.py b/airflow_lappis/dags/data_ingest/drop_tables_dag.py index 05751e20..89d3b0e1 100644 --- a/airflow_lappis/dags/data_ingest/drop_tables_dag.py +++ b/airflow_lappis/dags/data_ingest/drop_tables_dag.py @@ -19,7 +19,7 @@ tags=["siape", "admin", "drop"], params={ "tabela": Param("", description="Nome da tabela a ser dropada"), - "schema": Param("siape", description="Schema onde está a tabela"), + "schema": Param("", description="Schema onde está a tabela"), }, ) def drop_tabela_parametrizada_dag() -> None: @@ -29,9 +29,9 @@ def drop_tabela_parametrizada_dag() -> None: """ @task - def drop_tabela(params: Dict[str, str]) -> None: - tabela = params.get("tabela") - schema = params.get("schema") + def drop_tabela(task_params: Dict[str, str]) -> None: + tabela = task_params.get("tabela") + schema = task_params.get("schema") if not tabela: raise ValueError("Parâmetro 'tabela' não informado.") @@ -49,7 +49,7 @@ def drop_tabela(params: Dict[str, str]) -> None: logging.error(f"Erro ao remover tabela {schema}.{tabela}: {e}") raise - drop_tabela(drop_tabela_parametrizada_dag.params) + drop_tabela({"tabela": "{{ params.tabela }}", "schema": "{{ params.schema }}"}) dag_instance = drop_tabela_parametrizada_dag() From e4f759aa60a34bd4adf1351c4df25bdc8d27ac78 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Mon, 12 May 2025 13:42:11 -0300 Subject: [PATCH 091/317] Revert "fix(drop): ajusta dag de drop" This reverts commit 5d07400cd80c33150646646794a2c9e1c25ef117. --- airflow_lappis/dags/data_ingest/drop_tables_dag.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/drop_tables_dag.py b/airflow_lappis/dags/data_ingest/drop_tables_dag.py index 89d3b0e1..05751e20 100644 --- a/airflow_lappis/dags/data_ingest/drop_tables_dag.py +++ b/airflow_lappis/dags/data_ingest/drop_tables_dag.py @@ -19,7 +19,7 @@ tags=["siape", "admin", "drop"], params={ "tabela": Param("", description="Nome da tabela a ser dropada"), - "schema": Param("", description="Schema onde está a tabela"), + "schema": Param("siape", description="Schema onde está a tabela"), }, ) def drop_tabela_parametrizada_dag() -> None: @@ -29,9 +29,9 @@ def drop_tabela_parametrizada_dag() -> None: """ @task - def drop_tabela(task_params: Dict[str, str]) -> None: - tabela = task_params.get("tabela") - schema = task_params.get("schema") + def drop_tabela(params: Dict[str, str]) -> None: + tabela = params.get("tabela") + schema = params.get("schema") if not tabela: raise ValueError("Parâmetro 'tabela' não informado.") @@ -49,7 +49,7 @@ def drop_tabela(task_params: Dict[str, str]) -> None: logging.error(f"Erro ao remover tabela {schema}.{tabela}: {e}") raise - drop_tabela({"tabela": "{{ params.tabela }}", "schema": "{{ params.schema }}"}) + drop_tabela(drop_tabela_parametrizada_dag.params) dag_instance = drop_tabela_parametrizada_dag() From 31d74a0557406266a63b421ce8609ca19570fc63 Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Mon, 12 May 2025 17:09:22 +0000 Subject: [PATCH 092/317] Fix/ajuste drop --- airflow_lappis/dags/data_ingest/drop_tables_dag.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/drop_tables_dag.py b/airflow_lappis/dags/data_ingest/drop_tables_dag.py index 05751e20..89d3b0e1 100644 --- a/airflow_lappis/dags/data_ingest/drop_tables_dag.py +++ b/airflow_lappis/dags/data_ingest/drop_tables_dag.py @@ -19,7 +19,7 @@ tags=["siape", "admin", "drop"], params={ "tabela": Param("", description="Nome da tabela a ser dropada"), - "schema": Param("siape", description="Schema onde está a tabela"), + "schema": Param("", description="Schema onde está a tabela"), }, ) def drop_tabela_parametrizada_dag() -> None: @@ -29,9 +29,9 @@ def drop_tabela_parametrizada_dag() -> None: """ @task - def drop_tabela(params: Dict[str, str]) -> None: - tabela = params.get("tabela") - schema = params.get("schema") + def drop_tabela(task_params: Dict[str, str]) -> None: + tabela = task_params.get("tabela") + schema = task_params.get("schema") if not tabela: raise ValueError("Parâmetro 'tabela' não informado.") @@ -49,7 +49,7 @@ def drop_tabela(params: Dict[str, str]) -> None: logging.error(f"Erro ao remover tabela {schema}.{tabela}: {e}") raise - drop_tabela(drop_tabela_parametrizada_dag.params) + drop_tabela({"tabela": "{{ params.tabela }}", "schema": "{{ params.schema }}"}) dag_instance = drop_tabela_parametrizada_dag() From be6a9aab9b6310036853a89fde1cff8530f073c5 Mon Sep 17 00:00:00 2001 From: VictorSzk Date: Mon, 12 May 2025 18:54:50 -0300 Subject: [PATCH 093/317] Adicionando num_tranf --- .../ted_dbt/gold/ted_resumo_orcamentario.sql | 50 ++++++++++++++----- .../models/ted_dbt/silver/nc_plano_acao.sql | 2 +- .../models/ted_dbt/silver/pf_plano_acao.sql | 1 + 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/ted_resumo_orcamentario.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/ted_resumo_orcamentario.sql index 4a261167..cb08401d 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/ted_resumo_orcamentario.sql +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/ted_resumo_orcamentario.sql @@ -4,7 +4,7 @@ with -- Valor firmado valor_firmado_tb as ( - select id_plano_acao as plano_acao, vl_total_plano_acao as valor_firmado_tb + select id_plano_acao as plano_acao, vl_total_plano_acao as valor_firmado from {{ ref("planos_acao") }} ), @@ -13,6 +13,7 @@ with valores_orcamentos_tb as ( select plano_acao, + num_transf, sum( case when nc_evento not in ('300301', '300307') then nc_valor else 0 end ) as orcamento_recebido, @@ -21,7 +22,7 @@ with ) as orcamento_devolvido from {{ ref("nc_plano_acao") }} where ptres not in ('-9') - group by id_plano_acao + group by plano_acao, num_transf ), -- Destaque orçamentario = Orçamento recebido - Orçamento devolvido -- Destaque a receber = Valor firmado - Destaque orçamentario @@ -31,16 +32,19 @@ with valores_empenhados_tb as ( select plano_acao, + num_transf, sum( case when despesas_empenhadas > 0 then despesas_empenhadas else 0 end ) as empenhado, sum( case when despesas_empenhadas < 0 then - despesas_empenhadas else 0 end ) as empenho_anulado, - sum(despesas_pagas) as despesas_pagas + sum(despesas_pagas) as despesas_pagas_exercicio, + sum(restos_a_pagar_pagos) as despesas_pagas_rap, + sum(restos_a_pagar_inscritos) as restos_a_pagar, + sum(despesas_liquidadas) as despesas_liquidada from {{ ref("empenhos_plano_acao") }} - where abs(despesas_empenhadas) > 0 - group by id_plano_acao + group by plano_acao, num_transf ), -- Saldo empenho = Empenhado - Empenho anulado - Utilizado/pago @@ -50,6 +54,7 @@ with valores_financeiro_tb as ( select plano_acao, + num_transf, sum( case when pf_acao = 'TRANSFERENCIA' then pf_valor_linha else 0 end ) as financeiro_recebido, @@ -60,14 +65,33 @@ with case when pf_acao = 'CANCELAMENTO' then pf_valor_linha else 0 end ) as financeiro_cancelado from {{ ref("pf_plano_acao") }} - group by plano_acao + group by plano_acao, num_transf + ), + -- Saldo financeiro = Financeiro recebido - Financeiro devolvido - Utilizado/pago + -- Financeiro a receber = Valor firmado - Financeiro recebido + Financeiro devolvido + join_parcial as ( + select * + from valores_orcamentos_tb + full join valores_empenhados_tb using (plano_acao, num_transf) + full join valores_financeiro_tb using (plano_acao, num_transf) + ) --- Saldo financeiro = Financeiro recebido - Financeiro devolvido - Utilizado/pago --- Financeiro a receber = Valor firmado - Financeiro recebido + Financeiro devolvido -- Final -select * +select + plano_acao, + num_transf, + valor_firmado, + orcamento_recebido, + orcamento_devolvido, + empenhado, + empenho_anulado, + despesas_pagas_exercicio, + despesas_pagas_rap, + restos_a_pagar, + despesas_liquidada, + financeiro_recebido, + financeiro_devolvido, + financeiro_cancelado from valor_firmado_tb -left join valores_orcamentos_tb using (plano_acao) -left join valores_empenhados_tb using (plano_acao) -left join valores_financeiro_tb using (plano_acao) -where id_plano_acao is not null +full join join_parcial using (plano_acao) +where (plano_acao is not null) or (num_transf is not null) diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/nc_plano_acao.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/nc_plano_acao.sql index 05db3dd1..bb72e276 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/nc_plano_acao.sql +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/nc_plano_acao.sql @@ -12,7 +12,7 @@ with select pda.plano_acao, emissao_dia, - nc_transferencia, + nc_transferencia as num_transf, right(nc, 12) as nc, nc_fonte_recursos, ptres, diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/pf_plano_acao.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/pf_plano_acao.sql index ad90dee3..21031e44 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/pf_plano_acao.sql +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/pf_plano_acao.sql @@ -3,6 +3,7 @@ with programacoes_financeira as ( select pf, + pf_inscricao as num_transf, emissao_mes, emissao_dia, ug_emitente, From d3381940bf41a114fa97628b48e93ad86d227028 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Tue, 13 May 2025 20:15:53 -0300 Subject: [PATCH 094/317] feat(dbt): adiciona num_transf como parte do cruzamento de pfs --- .../models/ted_dbt/silver/pf_plano_acao.sql | 24 ++++++++++++------- .../ted_dbt/views/num_transf_n_plano_acao.sql | 18 ++++++++++---- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/pf_plano_acao.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/pf_plano_acao.sql index 21031e44..c60f078e 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/pf_plano_acao.sql +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/pf_plano_acao.sql @@ -17,18 +17,26 @@ with pf_transfere_gov as ( select - id_plano_acao as plano_acao, + tx_numero_programacao as pf, ug_emitente_programacao as ug_emitente, - tx_numero_programacao as pf + id_plano_acao as plano_acao from {{ source("transfere_gov", "programacao_financeira") }} ), - joined_table as ( - select * - from programacoes_financeira - inner join pf_transfere_gov using (pf, ug_emitente) + joined_by_transfere_gov as ( + select pf.*, t.plano_acao + from programacoes_financeira pf + inner join pf_transfere_gov t using (pf, ug_emitente) + ), + + joined_by_num_transf as ( + select pf.*, v.plano_acao + from programacoes_financeira pf + inner join {{ ref("num_transf_n_plano_acao") }} v using (num_transf) ) --- select * -from joined_table +from joined_by_transfere_gov +union +select * +from joined_by_num_transf diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/views/num_transf_n_plano_acao.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/views/num_transf_n_plano_acao.sql index 49a3b49b..b413f619 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/views/num_transf_n_plano_acao.sql +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/views/num_transf_n_plano_acao.sql @@ -15,12 +15,22 @@ with where nc_transferencia != '-8' ), - result_table as ( + joined as ( select distinct num_transf, id_plano_acao as plano_acao from nc_siafi left join nc_transfere_gov using (nc, ug) + ), + + ranked as ( + select + *, + row_number() over ( + partition by num_transf + order by case when plano_acao is not null then 1 else 2 end + ) as rn + from joined ) --- -select * -from result_table +select num_transf, plano_acao +from ranked +where rn = 1 From a2c9593f9685e7ddd2781ab7e1e71b22195fa0ea Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Wed, 14 May 2025 18:25:24 +0000 Subject: [PATCH 095/317] feat(pk): adiciona colunas de despesa como pk em empenhos --- airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py | 3 +++ .../dbt/ipea/models/contratos_dbt/bronze/empenhos_tesouro.sql | 3 +++ 2 files changed, 6 insertions(+) diff --git a/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py index f6e3a638..6b585352 100755 --- a/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py @@ -114,6 +114,9 @@ def insert_data_to_db(**context: Dict[str, Any]) -> None: "ne_ccor_ano_emissao", "emissao_dia", "emissao_mes", + "despesas_empenhadas", + "despesas_liquidadas", + "despesas_pagas", ] db.insert_data( diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos_tesouro.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos_tesouro.sql index 67829675..84329c31 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos_tesouro.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos_tesouro.sql @@ -7,6 +7,9 @@ "ne_ccor_ano_emissao", "emissao_dia", "emissao_mes", + "despesas_empenhadas", + "despesas_liquidadas", + "despesas_pagas", ], incremental_strategy="merge", ) From 6a3ce71809bcb2e53fe92b4a8e212a8e449b9899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Henrique=20Egewarth?= Date: Wed, 14 May 2025 19:29:42 +0000 Subject: [PATCH 096/317] =?UTF-8?q?Fix:=20alterando=20regex=20de=20numero?= =?UTF-8?q?=20de=20transferencia=20para=20pegar=20novos=20cen=C3=A1rios=20?= =?UTF-8?q?das=20NEs=20de=202022?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dags/dbt/ipea/models/ted_dbt/silver/empenhos_plano_acao.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/empenhos_plano_acao.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/empenhos_plano_acao.sql index 9e6827cb..9f3ef535 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/empenhos_plano_acao.sql +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/empenhos_plano_acao.sql @@ -7,7 +7,7 @@ with ( regexp_match( ne_ccor_descricao, - '(FERENCIA|NUMERO|Nº|TED|CRICAO|TRANSF.)(\s|^|-|)([0-9]{6}|1\w{5})(\s|$|\.|,|-|\/)' + '(FERENCIA|NUMERO|Nº|TED|CRICAO|TRANSF.)(\s|^|-|)([0-9]{6}|1\w{5}|[0-9]{3}\.[0-9]{3})(\s|$|\.|,|-|\/)' ) )[3] as num_transf, {{ target.schema }}.format_nc( From 350f48c6c83f08f10dee77918bb9567ec00da536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Henrique=20Egewarth?= Date: Wed, 14 May 2025 20:06:06 +0000 Subject: [PATCH 097/317] =?UTF-8?q?Fix:=20Fix:=20alterando=20regex=20de=20?= =?UTF-8?q?numero=20de=20transferencia=20para=20pegar=20novos=20cen=C3=A1r?= =?UTF-8?q?ios=20das=20NEs=20de=202022?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dags/dbt/ipea/models/ted_dbt/silver/empenhos_plano_acao.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/empenhos_plano_acao.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/empenhos_plano_acao.sql index 9f3ef535..ea1449c2 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/empenhos_plano_acao.sql +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/empenhos_plano_acao.sql @@ -7,7 +7,7 @@ with ( regexp_match( ne_ccor_descricao, - '(FERENCIA|NUMERO|Nº|TED|CRICAO|TRANSF.)(\s|^|-|)([0-9]{6}|1\w{5}|[0-9]{3}\.[0-9]{3})(\s|$|\.|,|-|\/)' + '(FERENCIA|NUMERO|Nº|TED|CRICAO|TRANSF.)(\s|^|-|)([0-9]{6}|1\w{5}|([0-9]{3}?:\.[0-9]{3}))(\s|$|\.|,|-|\/)' ) )[3] as num_transf, {{ target.schema }}.format_nc( From 8000e9a947f5b5085bfa58a4ffebc96b7accd2f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Henrique=20Egewarth?= Date: Wed, 14 May 2025 21:57:44 +0000 Subject: [PATCH 098/317] =?UTF-8?q?alterando=20regex=20de=20num=20de=20tra?= =?UTF-8?q?nsferencia=20para=20pegar=20mais=20cen=C3=A1rios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ted_dbt/silver/empenhos_plano_acao.sql | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/empenhos_plano_acao.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/empenhos_plano_acao.sql index ea1449c2..cfaff6ab 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/empenhos_plano_acao.sql +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/empenhos_plano_acao.sql @@ -4,12 +4,16 @@ with *, -- Uma série de extrações que servirão de identificadores right(ne_ccor, 12) as ne, - ( - regexp_match( - ne_ccor_descricao, - '(FERENCIA|NUMERO|Nº|TED|CRICAO|TRANSF.)(\s|^|-|)([0-9]{6}|1\w{5}|([0-9]{3}?:\.[0-9]{3}))(\s|$|\.|,|-|\/)' - ) - )[3] as num_transf, + replace( + ( + regexp_match( + ne_ccor_descricao, + '(FERENCIA|NUMERO|Nº|TED|CRICAO|TRANSF.|CAO|TRANSFERENCIA )(\s|^|-|)([0-9]{6}|1\w{5}|[0-9]{3}\.[0-9]{3})(\s|$|\.|,|-|\/)' + ) + )[3], + '.', + '' + ) as num_transf, {{ target.schema }}.format_nc( regexp_substr(ne_ccor_descricao, '([0-9]{4}NC[0-9]+)') ) as nc From aa6f0db65bdf0f01b468f5be58cee00c40cd466a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Henrique=20Egewarth?= Date: Fri, 16 May 2025 13:25:32 +0000 Subject: [PATCH 099/317] =?UTF-8?q?Adicionando=20coluna=20de=20benefici?= =?UTF-8?q?=C3=A1rio/emitente=20do=20TED.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dbt/ipea/models/ted_dbt/bronze/planos_acao.sql | 5 +++++ .../ted_dbt/gold/ted_resumo_orcamentario.sql | 14 +++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/planos_acao.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/planos_acao.sql index ef3e4687..1a4c3147 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/planos_acao.sql +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/planos_acao.sql @@ -5,6 +5,11 @@ with id_plano_acao, id_programa, sigla_unidade_descentralizada, + case + when sigla_unidade_descentralizada = 'IPEA' + then 'beneficiario' + else 'emitente' + end as ted_beneficiario_emitente, unidade_descentralizada, sigla_unidade_responsavel_execucao, unidade_responsavel_execucao, diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/ted_resumo_orcamentario.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/ted_resumo_orcamentario.sql index cb08401d..9b66c50b 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/ted_resumo_orcamentario.sql +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/ted_resumo_orcamentario.sql @@ -4,7 +4,11 @@ with -- Valor firmado valor_firmado_tb as ( - select id_plano_acao as plano_acao, vl_total_plano_acao as valor_firmado + select + id_plano_acao as plano_acao, + vl_total_plano_acao as valor_firmado, + sigla_unidade_descentralizada, + ted_beneficiario_emitente from {{ ref("planos_acao") }} ), @@ -80,6 +84,14 @@ with select plano_acao, num_transf, + sigla_unidade_descentralizada, + case + when ted_beneficiario_emitente = 'beneficiario' + then 'beneficiario' + when ted_beneficiario_emitente = 'emitente' + then 'emitente' + else 'nao_indicado' + end as ted_beneficiario_emitente, valor_firmado, orcamento_recebido, orcamento_devolvido, From 447757a94af75216b77675531825c4e113e01e68 Mon Sep 17 00:00:00 2001 From: VictorSzk Date: Mon, 19 May 2025 17:38:18 -0300 Subject: [PATCH 100/317] Nova coluna --- .../contratos_dbt/silver/estagios_mensal.sql | 87 ++++++++++++++++--- 1 file changed, 77 insertions(+), 10 deletions(-) diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/estagios_mensal.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/estagios_mensal.sql index 9530dca5..86a14769 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/estagios_mensal.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/estagios_mensal.sql @@ -3,29 +3,41 @@ with parsed_estagios as ( select right(ne_ccor, 12) as ne, - emissao_mes as mes_lancamento, + case when emissao_dia like '000/%' then true else false end as eh_rap, + case + when emissao_dia like '000/%' + then '01' + else substring(emissao_dia, '\/(\d{2})\/') + end as mes_lancamento, + right(emissao_mes, 4) as ano_lancamento, ne_ccor_favorecido as cnpj_cpf, substring(ne_info_complementar, '(^[0-9]+)') as info_complementar, ne_num_processo, despesas_empenhadas as valor_empenhado, despesas_liquidadas as valor_liquidado, - despesas_pagas as valor_pago + despesas_pagas as valor_pago, + restos_a_pagar_inscritos as restos_a_pagar, + restos_a_pagar_pagos as restos_a_pagar_pago from {{ ref("empenhos_tesouro") }} - where true and ne_ccor != 'Total' and emissao_mes not like '00%' + where true and ne_ccor != 'Total' ), grouped_estagios as ( select ne, + eh_rap, + ano_lancamento::integer as ano_lancamento, mes_lancamento, cnpj_cpf, max(info_complementar) as info_complementar, max(ne_num_processo) as num_processo, sum(valor_empenhado) as valor_empenhado, sum(valor_liquidado) as valor_liquidado, - sum(valor_pago) as valor_pago + sum(valor_pago) as valor_pago, + sum(restos_a_pagar) as restos_a_pagar, + sum(restos_a_pagar_pago) as restos_a_pagar_pago from parsed_estagios - group by 1, 2, 3 + group by 1, 2, 3, 4, 5 order by 1, 2 ), @@ -34,28 +46,83 @@ with ne, cnpj_cpf, info_complementar, - {{ target.schema }}.parse_date(mes_lancamento) as mes_lancamento, + eh_rap, + mes_lancamento, + ano_lancamento, + case + when eh_rap + then array[ano_lancamento - 1, ano_lancamento] + else array[ano_lancamento] + end as ano_efetivo, min(num_processo) over (partition by ne) as num_processo, valor_empenhado, valor_liquidado, - valor_pago + valor_pago, + restos_a_pagar, + restos_a_pagar_pago from grouped_estagios ), - cummulative_values as ( + unnest_rap as ( select ne, cnpj_cpf, info_complementar, + eh_rap, mes_lancamento, + ano_lancamento, + unnest(ano_efetivo) as ano_efetivo, num_processo, valor_empenhado, valor_liquidado, - valor_pago + valor_pago, + restos_a_pagar, + restos_a_pagar_pago from processo_fixed + ), + + fix_data as ( + select + ne, + cnpj_cpf, + info_complementar, + eh_rap, + case + when ano_efetivo = ano_lancamento then mes_lancamento else '12' + end as mes_lancamento, + ano_lancamento, + ano_efetivo, + num_processo, + valor_empenhado, + valor_liquidado, + valor_pago, + case + when ano_efetivo > ano_lancamento + then restos_a_pagar + else - restos_a_pagar + end as restos_a_pagar, + restos_a_pagar_pago + from unnest_rap + ), + + results as ( + select + ne, + cnpj_cpf, + info_complementar, + num_processo, + to_date( + ano_efetivo || '-' || mes_lancamento || '-01', 'YYYY-MM-DD' + ) as mes_lancamento, + valor_empenhado, + valor_liquidado, + valor_pago, + restos_a_pagar, + restos_a_pagar_pago + from fix_data ) -- select * -from cummulative_values +from results order by ne, cnpj_cpf, mes_lancamento From 181c1e654d31deb5cea6877706a1b70b2c5a5c73 Mon Sep 17 00:00:00 2001 From: VictorSzk Date: Tue, 20 May 2025 13:25:30 -0300 Subject: [PATCH 101/317] Arrumando novas colunas --- .../models/contratos_dbt/bronze/estagios.sql | 101 ------------------ .../gold/contratos_comparativo_mensal.sql | 8 +- .../silver/contratos_estagios.sql | 22 +++- .../contratos_dbt/silver/estagios_mensal.sql | 2 +- 4 files changed, 25 insertions(+), 108 deletions(-) delete mode 100755 airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/estagios.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/estagios.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/estagios.sql deleted file mode 100755 index c361cade..00000000 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/estagios.sql +++ /dev/null @@ -1,101 +0,0 @@ -{{ - config( - unique_key=[ - "ne_ccor", - "doc_observacao", - "natureza_despesa_detalhada", - "mes_lancamento", - ], - incremental_strategy="merge", - ) -}} - --- Comentário: O erro indica que há valores com parênteses "(660000.00)" que não podem --- ser convertidos para double precision --- O erro está ocorrendo nas colunas de valores monetários, especificamente em: --- - despesas_empenhadas_controle_empenho_saldo_moeda_origem --- - despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem --- - despesas_liquidadas_controle_empenho_saldo_moeda_origem --- - despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem --- - despesas_pagas_controle_empenho_saldo_moeda_origem --- - despesas_pagas_controle_empenho_movim_liquido_moeda_origem --- Precisamos modificar o CAST dessas colunas para tratar valores entre parênteses como --- números negativos -with - estagios_raw as ( - select - ne_ccor, - ne_informacao_complementar::text, - - -- Remove o "0" inicial e pontos, barras e hífens do número do processo - ne_ccor_descricao::text, - - doc_observacao::text, - natureza_despesa_1, - - natureza_despesa_detalhada_1, - - ne_ccor_favorecido, - - ne_ccor_favorecido_1, - - ne_ccor_mes_emissao, - mes_lancamento, - case - when length(ne_num_processo::text) > 3 - then regexp_replace(ne_num_processo::text, '[\./-]', '', 'g') - else ne_num_processo::text - end as ne_num_processo, - - case - when natureza_despesa::text ~ '^\d+$' then natureza_despesa::integer - end as natureza_despesa, - - case - when natureza_despesa_detalhada::text ~ '^\d+$' - then natureza_despesa_detalhada::integer - end as natureza_despesa_detalhada, - - case - when ano_lancamento::text ~ '^\d+$' then ano_lancamento::integer - end as ano_lancamento, - - case - when ne_ccor_ano_emissao::text ~ '^\d+$' then ne_ccor_ano_emissao::integer - end as ne_ccor_ano_emissao, - {{ - parse_financial_value( - "despesas_empenhadas_controle_empenho_saldo_moeda_origem" - ) - }} as despesas_empenhadas_controle_empenho_saldo_moeda_origem, - {{ - parse_financial_value( - "despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem" - ) - }} as despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem, - {{ - parse_financial_value( - "despesas_liquidadas_controle_empenho_saldo_moeda_origem" - ) - }} as despesas_liquidadas_controle_empenho_saldo_moeda_origem, - {{ - parse_financial_value( - "despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem" - ) - }} as despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem, - {{ - parse_financial_value( - "despesas_pagas_controle_empenho_saldo_moeda_origem" - ) - }} as despesas_pagas_controle_empenho_saldo_moeda_origem, - {{ - parse_financial_value( - "despesas_pagas_controle_empenho_movim_liquido_moeda_origem" - ) - }} as despesas_pagas_controle_empenho_movim_liquido_moeda_origem - - from {{ source("siafi", "estagios_tesouro") }} - ) - -select * -from estagios_raw diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql index 0522cdb6..eeaa1116 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql @@ -17,7 +17,9 @@ with c.saldo_contratual_disponivel as comprasgov_saldo_contratual_disponivel, s.valor_empenhado as siafi_valor_empenhado, s.valor_liquidado as siafi_valor_liquidado, - s.valor_pago as siafi_valor_pago + s.valor_pago as siafi_valor_pago, + s.restos_a_pagar as siafi_restos_a_pagar, + s.restos_a_pagar_pago as siafi_restos_a_pagar_pago from compras_gov_data as c full join siafi_data as s using (contrato_id, mes_ref) @@ -34,6 +36,8 @@ select comprasgov_saldo_contratual_disponivel, siafi_valor_empenhado, siafi_valor_liquidado, - siafi_valor_pago + siafi_valor_pago, + siafi_restos_a_pagar, + siafi_restos_a_pagar_pago from partial_result full join preenchimento using (contrato_id, mes_ref) diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_estagios.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_estagios.sql index aa08ef10..38141252 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_estagios.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_estagios.sql @@ -19,7 +19,9 @@ with mes_lancamento, valor_empenhado, valor_liquidado, - valor_pago + valor_pago, + restos_a_pagar, + restos_a_pagar_pago from {{ ref("estagios_mensal") }} left join id_table_1 using (ne, cnpj_cpf) ), @@ -48,7 +50,9 @@ with mes_lancamento, valor_empenhado, valor_liquidado, - valor_pago + valor_pago, + restos_a_pagar, + restos_a_pagar_pago from empenhos_restantes_1 l left join id_table_2 r using (cnpj_cpf, num_processo) ), @@ -75,7 +79,9 @@ with mes_lancamento, valor_empenhado, valor_liquidado, - valor_pago + valor_pago, + restos_a_pagar, + restos_a_pagar_pago from empenhos_restantes_2 l left join id_table_3 r using (cnpj_cpf, info_complementar) ), @@ -92,7 +98,15 @@ with ) -- -select contrato_id, mes_lancamento, valor_empenhado, valor_liquidado, valor_pago +select + contrato_id, + mes_lancamento, + sum(valor_empenhado) as valor_empenhado, + sum(valor_liquidado) as valor_liquidado, + sum(valor_pago) as valor_pago, + sum(restos_a_pagar) as restos_a_pagar, + sum(restos_a_pagar_pago) as restos_a_pagar_pago from result_table where contrato_id is not null +group by 1, 2 order by contrato_id, mes_lancamento diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/estagios_mensal.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/estagios_mensal.sql index 86a14769..67ae85f0 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/estagios_mensal.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/estagios_mensal.sql @@ -97,7 +97,7 @@ with valor_liquidado, valor_pago, case - when ano_efetivo > ano_lancamento + when ano_efetivo = ano_lancamento then restos_a_pagar else - restos_a_pagar end as restos_a_pagar, From 06e28d93bac100a84770d666adddf01f31639a73 Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Wed, 21 May 2025 16:25:39 +0000 Subject: [PATCH 102/317] Feat dbt docs --- .../dags/dbt/ipea/models/schema.yml | 1199 ++++++++++++++++- 1 file changed, 1170 insertions(+), 29 deletions(-) diff --git a/airflow_lappis/dags/dbt/ipea/models/schema.yml b/airflow_lappis/dags/dbt/ipea/models/schema.yml index 744ab558..6bf8ac32 100644 --- a/airflow_lappis/dags/dbt/ipea/models/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/schema.yml @@ -13,10 +13,75 @@ models: Tabela com informações sobre contratos, incluindo detalhes como o valor do contrato, a data de início e término, e o status do contrato. Esta tabela é fundamental para entender a execução e o cumprimento dos contratos firmados. A tabela é atualizada diariamente e contém dados de contratos firmados pelo IPEA. + A tabela realiza validações e limpezas dos dados extraídos do ComprasGov, incluindo formatação adequada de valores numéricos, + remoção de caracteres especiais em CPF/CNPJ e normalização de datas. columns: - name: id description: > Identificador único do contrato, utilizado para referenciar o contrato em outras tabelas e análises. + - name: receita_despesa + description: > + Indica se o contrato é de receita ou despesa. + - name: numero + description: > + Número do contrato no sistema ComprasGov. + - name: fornecedor_tipo + description: > + Tipo do fornecedor (PF, PJ, IDGENERICO para empresas do exterior). + - name: fornecedor_nome + description: > + Nome do fornecedor contratado. + - name: tipo + description: > + Tipo de contrato (ex: Contrato, Empenho, Termo de Compromisso, etc.). + - name: situacao + description: > + Situação atual do contrato (ex: Ativo, Inativo). + - name: categoria + description: > + Categoria do contrato (ex: Informática, Serviços, Mão de Obra, Serviços de Engenharia, etc.). + - name: objeto + description: > + Descrição do objeto contratado. + - name: codigo_modalidade + description: > + Código que identifica a modalidade do contrato. + - name: modalidade + description: > + Modalidade do contrato (ex: Pregão, Dispensa, Inexigibilidade, etc.). + - name: num_parcelas + description: > + Número de parcelas para pagamento do contrato. + - name: valor_inicial + description: > + Valor inicial do contrato, formatado como numérico. + - name: valor_global + description: > + Valor total do contrato após eventuais aditivos, formatado como numérico. + - name: valor_parcela + description: > + Valor de cada parcela do contrato, formatado como numérico. + - name: valor_acumulado + description: > + Valor acumulado já realizado do contrato, formatado como numérico. + - name: fornecedor_cnpj_cpf_idgener + description: > + CNPJ, CPF ou identificador genérico do fornecedor, com formatação padronizada para remoção de caracteres especiais. + - name: processo + description: > + Número do processo administrativo relacionado ao contrato, formatado para remover caracteres especiais. + - name: data_assinatura + description: > + Data em que o contrato foi assinado, validada e convertida para formato de data padrão. + - name: data_publicacao + description: > + Data de publicação do contrato no Diário Oficial, validada e convertida para formato de data padrão. + - name: vigencia_inicio + description: > + Data de início da vigência do contrato, validada e convertida para formato de data padrão. + - name: vigencia_fim + description: > + Data de término da vigência do contrato, validada e convertida para formato de data padrão. data_tests: - row_count_match: source_table: compras_gov.contratos @@ -64,7 +129,31 @@ models: - name: cronogramas description: > - Essa tabela contém informações sobre as despesas mensais programadas cada contrato. + Essa tabela contém informações sobre as despesas mensais programadas para cada contrato. + Os dados são extraídos da tabela cronograma do ComprasGov, com valores formatados adequadamente. + Apresenta o planejamento financeiro do contrato distribuído em parcelas mensais. + columns: + - name: id + description: > + Identificador único do cronograma. + - name: contrato_id + description: > + Identificador do contrato relacionado ao cronograma. + - name: tipo + description: > + Tipo de cada item do cronograma. + - name: mesref + description: > + Mês de referência do cronograma. + - name: anoref + description: > + Ano de referência do cronograma. + - name: valor + description: > + Valor programado para o mês e ano de referência, formatado como numérico. + - name: vencimento + description: > + Data de vencimento da parcela do cronograma. data_tests: - verificacao_tipagem: nome_tabela: 'contratos.cronogramas' @@ -92,7 +181,62 @@ models: tipo_esperado: 'date' - name: faturas - description: "Essa tabela contém informações sobre as faturas emitidas mensalmente de cada contrato." + description: > + Essa tabela contém informações sobre as faturas emitidas mensalmente de cada contrato. + Os dados são extraídos da tabela faturas do ComprasGov com formatação adequada de valores numéricos e datas. + A tabela inclui um processamento adicional para extrair informações dos empenhos a partir do campo JSON dados_empenho. + columns: + - name: id + description: > + Identificador único da fatura. + - name: contrato_id + description: > + Identificador do contrato relacionado à fatura. + - name: numero + description: > + Número da fatura emitida pelo fornecedor. + - name: emissao + description: > + Data de emissão da fatura pelo fornecedor. + - name: prazo + description: > + Data limite para pagamento da fatura. + - name: vencimento + description: > + Data de vencimento da fatura. + - name: valor + description: > + Valor bruto da fatura, formatado como numérico. + - name: juros + description: > + Valor de juros aplicados à fatura, formatado como numérico. + - name: multa + description: > + Valor de multa aplicada à fatura, formatado como numérico. + - name: glosa + description: > + Valor de glosa aplicada à fatura, formatado como numérico. + - name: valorliquido + description: > + Valor líquido da fatura após subtrações de glosas, multas ou juros, formatado como numérico. + - name: situacao + description: > + Status atual da fatura (Pago ou Pendente). + - name: mesref + description: > + Mês de referência da fatura. + - name: anoref + description: > + Ano de referência da fatura. + - name: id_empenho + description: > + Identificador do empenho extraído do campo JSON dados_empenho. + - name: numero_empenho + description: > + Número do empenho relacionado à fatura, extraído do campo JSON dados_empenho e convertido para maiúsculas. + - name: valor_empenho + description: > + Valor do empenho extraído do campo JSON dados_empenho. data_tests: - verificacao_tipagem: nome_tabela: 'contratos.faturas' @@ -153,7 +297,52 @@ models: - name: empenhos description: > - Essa tabela contém informações sobre os empenhos de cada contrato. + Essa tabela contém informações sobre os empenhos de cada contrato extraídos do ComprasGov. + Os dados são formatados adequadamente para garantir consistência nos tipos numéricos e de data. + Registra os compromissos de pagamento assumidos pela administração para cada contrato. + columns: + - name: id + description: > + Identificador único do empenho no ComprasGov. + - name: contrato_id + description: > + Identificador do contrato relacionado ao empenho. + - name: nota_empenho + description: > + Número da nota de empenho registrada no sistema. + - name: credor + description: > + Nome ou razão social do credor do empenho. + - name: credor_obj_cnpj_cpf_idgener + description: > + CNPJ, CPF ou identificador genérico do credor do empenho. + - name: empenhado + description: > + Valor total empenhado, formatado como numérico. + - name: aliquidar + description: > + Valor a liquidar do empenho, formatado como numérico. + - name: liquidado + description: > + Valor já liquidado do empenho, formatado como numérico. + - name: pago + description: > + Valor já pago do empenho, formatado como numérico. + - name: rpinscrito + description: > + Valor inscrito em restos a pagar, formatado como numérico. + - name: rpaliquidar + description: > + Valor inscrito em restos a pagar a liquidar, formatado como numérico. + - name: rpliquidado + description: > + Valor inscrito em restos a pagar já liquidado, formatado como numérico. + - name: rppago + description: > + Valor inscrito em restos a pagar já pago, formatado como numérico. + - name: data_emissao + description: > + Data de emissão do empenho, validada e convertida para formato de data padrão. data_tests: - row_count_match: source_table: compras_gov.empenhos @@ -195,11 +384,48 @@ models: nome_coluna: 'data_emissao' tipo_esperado: 'date' - - name: empenhos_tesouro description: > Essa tabela contém informações sobre movimentação financeira dos empenhos extraídos do SIAFI. - + Contém dados de empenhos do Tesouro Nacional com formatação de valores financeiros através da macro parse_financial_value. + Apresenta valores para as diferentes etapas da execução orçamentária (empenho, liquidação, pagamento). + columns: + - name: emissao_mes + description: > + Mês de emissão do empenho. + - name: emissao_dia + description: > + Data específica de emissão do empenho. + - name: ne_ccor + description: > + Número completo da nota de empenho no SIAFI. + - name: ne_num_processo + description: > + Número do processo relacionado ao empenho, com formatação para remover caracteres especiais. + - name: ne_info_complementar + description: > + Informações complementares sobre o empenho. + - name: ne_ccor_favorecido + description: > + CNPJ ou CPF do favorecido, formatado em caixa alta. + - name: ne_ccor_ano_emissao + description: > + Ano de emissão do empenho, filtrado para incluir apenas empenhos a partir do ano 2000. + - name: despesas_empenhadas + description: > + Valor das despesas empenhadas, formatado como numérico. + - name: despesas_liquidadas + description: > + Valor das despesas liquidadas, formatado como numérico. + - name: despesas_pagas + description: > + Valor das despesas pagas, formatado como numérico. + - name: restos_a_pagar_inscritos + description: > + Valor dos restos a pagar inscritos, formatado como numérico. + - name: restos_a_pagar_pagos + description: > + Valor dos restos a pagar pagos, formatado como numérico. data_tests: - verificacao_tipagem: nome_tabela: 'contratos.empenhos_tesouro' @@ -228,53 +454,96 @@ models: - name: estagios description: > - Essa tabela contém informações sobre os evento de cada empenho discriminados por mês. + Essa tabela contém informações sobre os eventos de cada empenho discriminados por mês. + Extrai dados da tabela estagios_tesouro do SIAFI com formatação e padronização para facilitar análises. + Trata especificamente valores monetários com parênteses, convertendo-os para números negativos. + Apresenta os diferentes estágios da despesa (empenho, liquidação, pagamento) ao longo do tempo. + columns: + - name: ne_ccor + description: > + Número completo da nota de empenho no SIAFI. + - name: ne_informacao_complementar + description: > + Informações complementares sobre o empenho. + - name: doc_observacao + description: > + Observações adicionais registradas no documento. + - name: ne_ccor_favorecido + description: > + CNPJ ou CPF do favorecido do empenho. + - name: mes_lancamento + description: > + Mês em que o lançamento foi registrado no SIAFI. + - name: ne_num_processo + description: > + Número do processo relacionado ao empenho, formatado para remover caracteres especiais. + - name: natureza_despesa + description: > + Código da natureza de despesa, convertido para integer quando possível. + - name: natureza_despesa_detalhada + description: > + Código detalhado da natureza de despesa, convertido para integer quando possível. + - name: ano_lancamento + description: > + Ano em que o lançamento foi registrado no SIAFI, convertido para integer quando possível. + - name: ne_ccor_ano_emissao + description: > + Ano de emissão do empenho, convertido para integer quando possível. + - name: despesas_empenhadas_controle_empenho_saldo_moeda_origem + description: > + Saldo das despesas empenhadas em moeda de origem, formatado como numérico. + - name: despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem + description: > + Movimento líquido das despesas empenhadas em moeda de origem, formatado como numérico. + - name: despesas_liquidadas_controle_empenho_saldo_moeda_origem + description: > + Saldo das despesas liquidadas em moeda de origem, formatado como numérico. + - name: despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem + description: > + Movimento líquido das despesas liquidadas em moeda de origem, formatado como numérico. + - name: despesas_pagas_controle_empenho_saldo_moeda_origem + description: > + Saldo das despesas pagas em moeda de origem, formatado como numérico. + - name: despesas_pagas_controle_empenho_movim_liquido_moeda_origem + description: > + Movimento líquido das despesas pagas em moeda de origem, formatado como numérico. data_tests: - verificacao_tipagem: nome_tabela: 'contratos.estagios' nome_coluna: 'natureza_despesa' tipo_esperado: 'integer' - - verificacao_tipagem: nome_tabela: 'contratos.estagios' nome_coluna: 'natureza_despesa_detalhada' tipo_esperado: 'integer' - - verificacao_tipagem: nome_tabela: 'contratos.estagios' nome_coluna: 'ano_lancamento' tipo_esperado: 'integer' - - verificacao_tipagem: nome_tabela: 'contratos.estagios' nome_coluna: 'ne_ccor_ano_emissao' tipo_esperado: 'integer' - - verificacao_tipagem: nome_tabela: 'contratos.estagios' nome_coluna: 'despesas_empenhadas_controle_empenho_saldo_moeda_origem' tipo_esperado: 'numeric' - - verificacao_tipagem: nome_tabela: 'contratos.estagios' nome_coluna: 'despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem' tipo_esperado: 'numeric' - - verificacao_tipagem: nome_tabela: 'contratos.estagios' nome_coluna: 'despesas_liquidadas_controle_empenho_saldo_moeda_origem' tipo_esperado: 'numeric' - - verificacao_tipagem: nome_tabela: 'contratos.estagios' nome_coluna: 'despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem' tipo_esperado: 'numeric' - - verificacao_tipagem: nome_tabela: 'contratos.estagios' nome_coluna: 'despesas_pagas_controle_empenho_saldo_moeda_origem' tipo_esperado: 'numeric' - - verificacao_tipagem: nome_tabela: 'contratos.estagios' nome_coluna: 'despesas_pagas_controle_empenho_movim_liquido_moeda_origem' @@ -282,45 +551,469 @@ models: ## Silver - name: contratos_empenhos - description: Essa tabela combina as informações de movimentação financeira dos empenhos com os ids de contrato do IPEA. + description: > + Essa tabela combina as informações de movimentação financeira dos empenhos com os ids de contrato do IPEA. + Realiza uma complexa estratégia de join entre os contratos e empenhos do tesouro, tentando várias abordagens + para relacionar os dados do SIAFI com os contratos do ComprasGov usando: + 1) número de empenho e CNPJ/CPF, 2) número de processo, 3) CNPJ/CPF único, e 4) informação complementar. + columns: + - name: contrato_id + description: > + Identificador único do contrato relacionado ao empenho. + - name: ne_transformed + description: > + Número da nota de empenho padronizado para facilitar joins entre diferentes fontes. + - name: ne_ccor + description: > + Número completo da nota de empenho no SIAFI. + - name: ne_info_complementar + description: > + Informações complementares sobre o empenho no SIAFI. + - name: ne_num_processo + description: > + Número do processo administrativo relacionado ao empenho, formatado para remover caracteres especiais. + - name: ne_ccor_descricao + description: > + Descrição da nota de empenho conforme registrado no SIAFI. + - name: doc_observacao + description: > + Observações registradas no documento do empenho. + - name: natureza_despesa + description: > + Código da natureza da despesa do empenho. + - name: natureza_despesa_descricao + description: > + Descrição da natureza da despesa do empenho. + - name: ne_ccor_favorecido + description: > + CNPJ ou CPF do favorecido do empenho. + - name: ne_ccor_ano_emissao + description: > + Ano de emissão do empenho. + - name: despesas_empenhadas + description: > + Valor total das despesas empenhadas para o contrato. + - name: despesas_liquidadas + description: > + Valor total das despesas liquidadas para o contrato. + - name: despesas_pagas + description: > + Valor total das despesas pagas para o contrato. - name: contratos_estagios description: > Essa tabela combina as informações de movimentação financeira dos empenhos com os ids de contrato do IPEA mensalmente. - + Utiliza uma estratégia de join em cascata, tentando correlacionar os estágios das despesas do SIAFI com os + contratos do ComprasGov através de várias combinações de chaves, incluindo número de empenho, CNPJ/CPF, número + de processo e informações complementares. + columns: + - name: contrato_id + description: > + Identificador único do contrato relacionado aos estágios. + - name: mes_lancamento + description: > + Mês em que o estágio da despesa foi registrado no SIAFI. + - name: valor_empenhado + description: > + Valor empenhado no mês de referência para o contrato. + - name: valor_liquidado + description: > + Valor liquidado no mês de referência para o contrato. + - name: valor_pago + description: > + Valor pago no mês de referência para o contrato. + - name: estagios_mensal description: > Essa tabela discrimina os valores empenhados, liquidados e pagos mensalmente extraídos do SIAFI para cada contrato. - + Transforma e agrega os dados da tabela empenhos_tesouro do SIAFI, padronizando formatos e consolidando valores + por número de empenho, mês e CNPJ/CPF. + columns: + - name: ne + description: > + Número da nota de empenho no formato padronizado, extraído das 12 últimas posições do ne_ccor. + - name: mes_lancamento + description: > + Mês de referência do lançamento, convertido para formato de data padrão. + - name: cnpj_cpf + description: > + CNPJ ou CPF do favorecido do empenho. + - name: info_complementar + description: > + Informação complementar extraída do ne_info_complementar. + - name: num_processo + description: > + Número do processo administrativo relacionado ao empenho. + - name: valor_empenhado + description: > + Valor total empenhado no mês e para o empenho específico. + - name: valor_liquidado + description: > + Valor total liquidado no mês e para o empenho específico. + - name: valor_pago + description: > + Valor total pago no mês e para o empenho específico. + + - name: contratos_faturas + description: > + Essa tabela integra informações detalhadas de faturas com dados dos contratos relacionados. + Permite uma visão completa de cada fatura no contexto do seu contrato, incluindo dados como número de contrato, + processo, objeto e fornecedor. Facilita a análise financeira das faturas em conjunto com seus respectivos contratos. + columns: + - name: id + description: > + Identificador único da fatura. + - name: contrato_id + description: > + Identificador do contrato relacionado à fatura. + - name: numero_contrato + description: > + Número do contrato no sistema ComprasGov. + - name: contrato_processo + description: > + Número do processo administrativo relacionado ao contrato. + - name: fornecedor_cnpj_cpf_idgener + description: > + CNPJ, CPF ou identificador genérico do fornecedor do contrato. + - name: objeto_contrato + description: > + Descrição do objeto contratado. + - name: tipolistafatura_id + description: > + Identificador do tipo de lista de fatura. + - name: justificativafatura_id + description: > + Identificador da justificativa da fatura, quando aplicável. + - name: sfadrao_id + description: > + Identificador do padrão de sistema financeiro relacionado. + - name: numero + description: > + Número da fatura emitida pelo fornecedor. + - name: emissao + description: > + Data de emissão da fatura. + - name: prazo + description: > + Data limite para pagamento da fatura. + - name: vencimento + description: > + Data de vencimento da fatura. + - name: valor + description: > + Valor bruto da fatura. + - name: juros + description: > + Valor de juros aplicados à fatura. + - name: multa + description: > + Valor de multa aplicada à fatura. + - name: glosa + description: > + Valor de glosa aplicada à fatura. + - name: valorliquido + description: > + Valor líquido da fatura após subtrações de glosas, multas ou juros. + - name: processo + description: > + Número do processo administrativo específico da fatura, quando diferente do processo do contrato. + - name: protocolo + description: > + Data de protocolo da fatura no sistema. + - name: ateste + description: > + Data em que a fatura foi atestada, confirmando a entrega do produto ou serviço. + - name: repactuacao + description: > + Indica se a fatura está relacionada a uma repactuação contratual. + - name: infcomplementar + description: > + Informações complementares sobre a fatura. + - name: mesref + description: > + Mês de referência da fatura. + - name: anoref + description: > + Ano de referência da fatura. + - name: situacao + description: > + Status atual da fatura (Pago ou Pendente). + - name: chave_nfe + description: > + Chave da Nota Fiscal Eletrônica associada à fatura, quando disponível. + - name: dados_referencia + description: > + Dados de referência adicionais da fatura, geralmente em formato JSON. + - name: dados_item_faturado + description: > + Detalhamento dos itens faturados, geralmente em formato JSON. + - name: dados_empenho + description: > + Informações sobre o empenho relacionado à fatura, em formato JSON. + - name: id_empenho + description: > + Identificador do empenho extraído do campo JSON dados_empenho. + - name: numero_empenho + description: > + Número do empenho relacionado à fatura. + - name: valor_empenho + description: > + Valor do empenho relacionado à fatura. + - name: subelemento + description: > + Código do subelemento de despesa relacionado à fatura. + - name: cronogramas_fatura_mensal description: > - Essa tabela discrimina os falores programados e faturados mensalmente pelo ComprasGov para cada contrato. + Essa tabela discrimina os valores programados e faturados mensalmente pelo ComprasGov para cada contrato. + Combina dados dos cronogramas com as faturas pagas e pendentes, agrupando por contrato e mês de referência, + e calculando o saldo contratual disponível (diferença entre o valor programado e os valores faturados). + columns: + - name: contrato_id + description: > + Identificador único do contrato. + - name: mes_ref + description: > + Mês de referência dos valores programados e faturados. + - name: valor_cronograma + description: > + Valor total programado para o mês de referência conforme cronograma do contrato. + - name: valor_faturas_pagas + description: > + Valor total de faturas com status "Pago" no mês de referência. + - name: valor_faturas_pendentes + description: > + Valor total de faturas com status "Pendente" no mês de referência. + - name: saldo_contratual_disponivel + description: > + Diferença entre o valor programado no cronograma e a soma dos valores faturados (pagos e pendentes). ## Golds - name: contratos_resumo description: > Essa tabela contém um resumo dos contratos, informações contratuais como valor global e valor pago, e situação atual, como em vigência ou pendente de baixa. + Facilita a análise gerencial dos contratos com indicadores chave como status de pagamento, tipo de fornecedor, + e identificação de contratos continuados (com vigência superior a dois anos e mais de uma parcela). + columns: + - name: contrato_id + description: > + Identificador único do contrato. + - name: fornecedor_cnpj_cpf + description: > + CNPJ ou CPF do fornecedor contratado, com formatação padronizada. + - name: numero + description: > + Número do contrato no sistema ComprasGov. + - name: categoria + description: > + Categoria do contrato (ex: Informática, Serviços, Mão de Obra). + - name: modalidade + description: > + Modalidade de licitação utilizada na contratação. + - name: tipo + description: > + Tipo de contrato (ex: Contrato, Empenho, Termo de Compromisso). + - name: situacao + description: > + Situação atual do contrato (Ativo ou Inativo). + - name: pendente_baixa + description: > + Indica se o contrato está pendente de baixa ('Sim' quando o valor pago for igual ao valor global, 'Não' caso contrário). + - name: fornecedor_nome + description: > + Nome ou razão social do fornecedor contratado. + - name: objeto + description: > + Descrição detalhada do objeto contratado. + - name: valor_global + description: > + Valor total do contrato após eventuais aditivos. + - name: despesas_pagas + description: > + Valor total já pago do contrato conforme registros do SIAFI. + - name: vigencia_inicio + description: > + Data de início da vigência do contrato. + - name: vigencia_fim + description: > + Data de término da vigência do contrato. + - name: num_parcelas + description: > + Número de parcelas para pagamento do contrato. + - name: fornecedor_tipo + description: > + Tipo do fornecedor, categorizado como 'Empresa do Exterior' para IDGENERICO. + - name: Unidade + description: > + Unidade gestora responsável pelo contrato, no formato "código - nome_resumido". + - name: continuado + description: > + Indica se o contrato é de prestação continuada ('Sim' quando a vigência for maior que 730 dias e tiver mais de uma parcela). - name: contratos_comparativo_mensal description: > Essa tabela contém um comparativo mensal dos contratos, discriminando os valores empenhados, liquidados e pagos do SIAFI, programados e faturados do ComprasGov. + Permite a identificação de inconsistências entre os sistemas e o acompanhamento detalhado + da execução financeira mensal de cada contrato. + columns: + - name: contrato_id + description: > + Identificador único do contrato. + - name: mes_ref + description: > + Mês de referência da informação financeira, preenchido para todos os meses entre o início e fim do contrato. + - name: comprasgov_valor_cronograma + description: > + Valor programado para o mês conforme cronograma registrado no ComprasGov. + - name: comprasgov_valor_faturas + description: > + Soma dos valores das faturas (pagas e pendentes) no mês de referência conforme ComprasGov. + - name: comprasgov_saldo_contratual_disponivel + description: > + Diferença entre o valor programado e o valor faturado no mês, indicando o saldo disponível. + - name: siafi_valor_empenhado + description: > + Valor empenhado no mês de referência conforme registros do SIAFI. + - name: siafi_valor_liquidado + description: > + Valor liquidado no mês de referência conforme registros do SIAFI. + - name: siafi_valor_pago + description: > + Valor pago no mês de referência conforme registros do SIAFI. + + - name: contratos_somatorio + description: > + Essa tabela contém somatórios dos valores de cronograma, fatura, empenho, liquidação e pagamento para cada contrato. + Facilita análises agregadas sobre a execução financeira total de cada contrato, com indicadores importantes + como o orçamento pendente de execução. + columns: + - name: contrato_id + description: > + Identificador único do contrato. + - name: total_cronograma + description: > + Soma de todos os valores programados no cronograma do contrato. + - name: total_faturas + description: > + Soma de todos os valores faturados (pagos e pendentes) para o contrato. + - name: total_saldo_disponivel + description: > + Diferença total entre valores programados e faturados, indicando o saldo contratual disponível. + - name: orcamento_a_executar + description: > + Soma dos valores programados em meses onde ainda não houve faturamento, indicando o orçamento pendente de execução. + - name: total_empenhado + description: > + Soma de todos os valores empenhados para o contrato segundo o SIAFI. + - name: total_liquidado + description: > + Soma de todos os valores liquidados para o contrato segundo o SIAFI. + - name: total_pago + description: > + Soma de todos os valores pagos para o contrato segundo o SIAFI. ## View - name: identificadores description: > - Essa tabela contém os identificadores dos contratos, empenhos e faturas, - Essa tabela é utilizada para facilitar joins. + Essa tabela contém os identificadores dos contratos, empenhos e faturas. + Consolida os números de empenho encontrados em diferentes fontes (contratos, faturas, empenhos) + e padroniza informações como processo e CNPJ/CPF para facilitar joins entre as tabelas. + Também cria um campo info_complementar que combina unidade gestora, modalidade e número de contrato/licitação. + columns: + - name: contrato_id + description: > + Identificador único do contrato. + - name: categoria + description: > + Categoria do contrato (ex: Informática, Serviços, Mão de Obra). + - name: processo + description: > + Número do processo administrativo do contrato, formatado para remover caracteres não numéricos. + - name: cnpj_cpf + description: > + CNPJ ou CPF do fornecedor, formatado para remover caracteres especiais como barras, pontos e hífens. + - name: info_complementar + description: > + Informação complementar construída a partir da unidade gestora, código de modalidade e número do contrato ou licitação. + - name: ne + description: > + Número da nota de empenho relacionada ao contrato, combinando informações de contratos, empenhos e faturas. + # Pessoas DBT # TED DBT - name: pf_tesouro description: > - Dados das programações financeiras extraídas do SIAFI, com informações sobre o tipo de programação, a data de execução e o valor. - Essa tabela é atualizada diariamente. + Esta tabela contém registros de Programações Financeiras extraídas do SIAFI, detalhando informações como tipo de programação, data de execução e valor. + A tabela é atualizada diariamente e reflete as autorizações de movimentação financeira no âmbito da execução orçamentária federal. + columns: + - name: emissao_mes + description: > + Mês de emissão da Programação Financeira. + - name: emissao_dia + description: > + Dia de emissão da Programação Financeira. + - name: ug_emitente + description: > + Código da Unidade Gestora responsável pela emissão da Programação Financeira. + - name: ug_emitente_descricao + description: > + Nome ou descrição da Unidade Gestora emitente da Programação Financeira. + - name: ug_favorecido + description: > + Código da Unidade Gestora favorecida pela Programação Financeira. + - name: ug_favorecido_descricao + description: > + Nome ou descrição da Unidade Gestora favorecida pela Programação Financeira. + - name: pf_evento + description: > + Código do evento contábil associado à Programação Financeira, representando a natureza da transação. + - name: pf_evento_descricao + description: > + Descrição do evento contábil vinculado à Programação Financeira. + - name: pf + description: > + Número identificador único da Programação Financeira. + - name: pf_inscricao + description: > + Número de inscrição da Programação Financeira, utilizado para controle e acompanhamento. + - name: pf_acao + description: > + Código da ação orçamentária associada à Programação Financeira. + - name: pf_acao_descricao + description: > + Descrição da ação orçamentária vinculada à Programação Financeira. + - name: pf_fonte_recursos + description: > + Código da fonte de recursos associada à Programação Financeira, indicando a origem dos recursos utilizados. + - name: pf_fonte_recursos_descricao + description: > + Descrição textual da fonte de recursos vinculada à Programação Financeira. + - name: pf_vinculacao_pagamento + description: > + Código da vinculação de pagamento associada à Programação Financeira. + - name: pf_vinculacao_pagamento_descricao + description: > + Descrição da vinculação de pagamento vinculada à Programação Financeira. + - name: pf_categoria_gasto + description: > + Código da categoria de gasto associada à Programação Financeira, conforme classificação orçamentária. + - name: pf_recurso + description: > + Código do recurso associado à Programação Financeira. + - name: pf_recurso_descricao + description: > + Descrição do recurso vinculado à Programação Financeira. + - name: doc_observacao + description: > + Observações adicionais relacionadas à Programação Financeira. + - name: pf_valor_linha + description: > + Valor monetário individual da linha da Programação Financeira. data_tests: - verificacao_tipagem: nome_tabela: 'ted.pf_tesouro' @@ -331,20 +1024,468 @@ models: nome_coluna: 'pf_valor_linha' tipo_esperado: 'numeric' + - name: nc_tesouro + description: > + Esta tabela registra informações detalhadas sobre as Notas de Crédito emitidas no âmbito da execução orçamentária e financeira do governo federal. + As Notas de Crédito representam autorizações para a realização de despesas, sendo fundamentais para o controle e acompanhamento da execução orçamentária. + columns: + - name: emissao_mes + description: > + Mês de emissão da Nota de Crédito. + - name: emissao_dia + description: > + Dia de emissão da Nota de Crédito. + - name: nc + description: > + Número identificador único da Nota de Crédito. + - name: nc_transferencia + description: > + Indicador de que a Nota de Crédito refere-se a uma transferência de recursos entre unidades gestoras. + - name: nc_fonte_recursos + description: > + Código da fonte de recursos associada à Nota de Crédito, indicando a origem dos recursos utilizados. + - name: nc_fonte_recursos_descricao + description: > + Descrição textual da fonte de recursos vinculada à Nota de Crédito. + - name: ptres + description: > + Código do Plano Interno de Trabalho (PTRES) relacionado à Nota de Crédito, utilizado para detalhar a alocação dos recursos. + - name: nc_evento + description: > + Código do evento contábil associado à Nota de Crédito, representando a natureza da transação. + - name: nc_evento_descricao + description: > + Descrição do evento contábil vinculado à Nota de Crédito. + - name: ug_responsavel + description: > + Código da Unidade Gestora responsável pela emissão da Nota de Crédito. + - name: ug_responsavel_descricao + description: > + Nome ou descrição da Unidade Gestora responsável pela emissão da Nota de Crédito. + - name: natureza_despesa + description: > + Código da natureza da despesa associada à Nota de Crédito, conforme classificação orçamentária. + - name: natureza_despesa_detalhada + description: > + Descrição detalhada da natureza da despesa vinculada à Nota de Crédito. + - name: plano_interno + description: > + Código do plano interno relacionado à Nota de Crédito, utilizado para controle interno da execução orçamentária. + - name: plano_detalhado_descricao1 + description: > + Primeira descrição detalhada do plano interno associado à Nota de Crédito. + - name: plano_detalhado_descricao2 + description: > + Segunda descrição detalhada do plano interno associado à Nota de Crédito. + - name: favorecido_doc + description: > + Documento de identificação (CPF ou CNPJ) do favorecido pela Nota de Crédito. + - name: favorecido_doc_descricao + description: > + Nome ou razão social do favorecido identificado na Nota de Crédito. + - name: nc_valor_linha + description: > + Valor monetário individual da linha da Nota de Crédito. + - name: movimento_liquido + description: > + Valor líquido do movimento financeiro associado à Nota de Crédito, após deduções ou acréscimos aplicáveis. + + - name: planos_acao + description: > + Esta tabela armazena informações detalhadas sobre os Planos de Ação vinculados a programas públicos. + Ela consolida dados sobre as unidades envolvidas na execução, prazos de vigência, valores, justificativas, + formas de execução e instrumentos utilizados. Serve como base para o acompanhamento, análise e auditoria + da implementação de ações públicas em diferentes formatos de execução. + columns: + - name: id_plano_acao + description: > + Identificador único do Plano de Ação. + - name: id_programa + description: > + Identificador único do programa ao qual o Plano de Ação está vinculado. + - name: sigla_unidade_descentralizada + description: > + Sigla da unidade descentralizada responsável pelo Plano de Ação. + - name: unidade_descentralizada + description: > + Nome completo da unidade descentralizada responsável pelo Plano de Ação. + - name: sigla_unidade_responsavel_execucao + description: > + Sigla da unidade responsável pela execução do Plano de Ação. + - name: unidade_responsavel_execucao + description: > + Nome completo da unidade responsável pela execução do Plano de Ação. + - name: vl_total_plano_acao + description: > + Valor total previsto para execução do Plano de Ação. + - name: dt_inicio_vigencia + description: > + Data de início da vigência do Plano de Ação. + - name: dt_fim_vigencia + description: > + Data final da vigência do Plano de Ação. + - name: tx_objeto_plano_acao + description: > + Descrição do objeto do Plano de Ação, informando seu propósito e escopo. + - name: tx_justificativa_plano_acao + description: > + Justificativa para a elaboração e execução do Plano de Ação. + - name: in_forma_execucao_direta + description: > + Indicador booleano que sinaliza se a execução do plano ocorrerá de forma direta. + - name: in_forma_execucao_particulares + description: > + Indicador booleano que sinaliza se a execução contará com a participação de particulares. + - name: in_forma_execucao_descentralizada + description: > + Indicador booleano que sinaliza se a execução será realizada de forma descentralizada. + - name: tx_situacao_plano_acao + description: > + Situação atual do Plano de Ação (ex: em elaboração, aprovado, em execução, concluído). + - name: aa_ano_plano_acao + description: > + Ano de referência do Plano de Ação. + - name: vl_beneficiario_especifico + description: > + Valor destinado especificamente a beneficiários definidos no Plano de Ação. + - name: vl_chamamento_publico + description: > + Valor previsto para execução por meio de chamamento público. + - name: sq_instrumento + description: > + Código sequencial do instrumento jurídico ou administrativo associado ao Plano de Ação. + - name: aa_instrumento + description: > + Ano de referência do instrumento associado ao Plano de Ação. + + - name: nota_credito + description: > + Esta tabela contém informações sobre as Notas de Crédito emitidas no contexto dos Planos de Ação. + As notas de crédito representam movimentações financeiras entre unidades gestoras e gestões, sendo + vinculadas a um Plano de Ação específico. A tabela registra dados como número, data de emissão, + códigos das unidades e gestões envolvidas, além de observações e situação da nota. + columns: + - name: id_nota + description: > + Identificador único da Nota de Crédito. + - name: id_plano_acao + description: > + Identificador único do Plano de Ação ao qual a Nota de Crédito está vinculada. + - name: tx_minuta_nota + description: > + Texto ou rascunho da minuta da Nota de Crédito. + - name: tx_numero_nota + description: > + Número da Nota de Crédito, utilizado para controle e rastreabilidade. + - name: dt_emissao_nota + description: > + Data e hora da emissão da Nota de Crédito. + - name: cd_gestao_emitente_nota + description: > + Código da gestão pública que emitiu a Nota de Crédito. + - name: cd_gestao_favorecida_nota + description: > + Código da gestão pública favorecida pela Nota de Crédito. + - name: tx_situacao_nota + description: > + Situação atual da Nota de Crédito (ex: emitida, cancelada, aprovada). + - name: cd_ug_emitente_nota + description: > + Código da Unidade Gestora que emitiu a Nota de Crédito. + - name: cd_ug_favorecida_nota + description: > + Código da Unidade Gestora favorecida pela Nota de Crédito. + - name: tx_observacao_nota + description: > + Observações adicionais registradas na Nota de Crédito, quando houver. + + - name: programacao_financeira + description: > + Esta tabela armazena os registros de Programações Financeiras vinculadas aos Planos de Ação. + A Programação Financeira representa o planejamento e execução da alocação de recursos entre unidades gestoras. + A tabela contempla informações como tipo, número, situação, unidade emitente e favorecida, + bem como observações e o documento hábil de recebimento. + columns: + - name: id_programacao + description: > + Identificador único da Programação Financeira. + - name: id_plano_acao + description: > + Identificador único do Plano de Ação ao qual a Programação Financeira está vinculada. + - name: tp_pf_tipo_programacao + description: > + Tipo de Programação Financeira (ex: ordinária, suplementar, especial). + - name: tx_minuta_programacao + description: > + Minuta ou rascunho da Programação Financeira. + - name: tx_numero_programacao + description: > + Número da Programação Financeira, utilizado para controle interno e rastreamento. + - name: tx_situacao_programacao + description: > + Situação atual da Programação Financeira (ex: em análise, aprovada, cancelada). + - name: tx_observacao_programacao + description: > + Observações complementares sobre a Programação Financeira, se houver. + - name: ug_emitente_programacao + description: > + Código ou nome da Unidade Gestora que emitiu a Programação Financeira. + - name: ug_favorecida_programacao + description: > + Código ou nome da Unidade Gestora favorecida pela Programação Financeira. + - name: dh_recebimento_programacao + description: > + Data e hora do recebimento do documento hábil relacionado à Programação Financeira. + + - name: programas + description: > + Esta tabela contém os registros dos Programas institucionais, que orientam e organizam os Planos de Ação + sob responsabilidade de diferentes unidades da administração pública. A tabela agrega dados como código, + nome, ano, situação, descrição, objetivos e informações relacionadas às autorizações e tipos de investimentos, + além de indicar critérios específicos como beneficiários e chamamentos públicos. + columns: + - name: id_programa + description: > + Identificador único do Programa. + - name: tx_codigo_programa + description: > + Código atribuído ao Programa para fins de controle e identificação institucional. + - name: aa_ano_programa + description: > + Ano de referência do Programa. + - name: tx_situacao_programa + description: > + Situação atual do Programa (ex: ativo, encerrado, em elaboração). + - name: tx_nome_programa + description: > + Nome oficial do Programa. + - name: sigla_unidade_descentralizadora + description: > + Sigla da unidade responsável pela descentralização dos recursos do Programa. + - name: unidade_descentralizadora + description: > + Nome completo da unidade descentralizadora responsável pelo Programa. + - name: sigla_unidade_responsavel_acompanhamento + description: > + Sigla da unidade responsável pelo acompanhamento do Programa. + - name: unidade_responsavel_acompanhamento + description: > + Nome completo da unidade responsável pelo acompanhamento do Programa. + - name: tx_nome_institucional_programa + description: > + Nome institucional utilizado oficialmente para identificar o Programa. + - name: tx_objetivo_programa + description: > + Objetivo principal do Programa, descrevendo seu propósito estratégico. + - name: tx_descricao_programa + description: > + Descrição detalhada do Programa, abrangendo seu escopo e diretrizes. + - name: in_grupo_investimento_obra + description: > + Indicador booleano que sinaliza se o Programa está vinculado a investimento em obras. + - name: in_grupo_investimento_servico + description: > + Indicador booleano que sinaliza se o Programa está vinculado a investimento em serviços. + - name: in_grupo_investimento_equipamento + description: > + Indicador booleano que sinaliza se o Programa está vinculado a investimento em equipamentos. + - name: in_autoriza_subdescentralizacao_outro + description: > + Indicador que informa se o Programa autoriza subdescentralização para outras unidades. + - name: in_autoriza_realizacao_despesas + description: > + Indicador que informa se o Programa autoriza a realização de despesas diretamente. + - name: in_autoriza_execucao_creditos_descentralizada + description: > + Indicador que informa se o Programa autoriza a execução descentralizada de créditos. + - name: in_beneficiario_especifico + description: > + Indicador booleano de existência de beneficiário específico no Programa. + - name: dt_recebimento_plano_beneficiario_inicio + description: > + Data de início para recebimento do plano de beneficiário específico vinculado ao Programa. + - name: dt_recebimento_plano_beneficiario_fim + description: > + Data final para recebimento do plano de beneficiário específico vinculado ao Programa. + - name: in_chamamento_publico + description: > + Indicador booleano de execução via chamamento público no âmbito do Programa. + - name: dt_recebimento_plano_chamamento_inicio + description: > + Data de início para recebimento do plano relacionado a chamamento público. + - name: dt_recebimento_plano_chamamento_fim + description: > + Data final para recebimento do plano relacionado a chamamento público. + - name: empenhos_plano_acao description: > - Dados dos empenhos extraídos do SIAFI, incluído o identificador do plano de ação. - Essa tabela é atualizada diariamente. + Esta tabela estabelece a relação entre os empenhos registrados no SIAFI e os respectivos Planos de Ação, + permitindo o rastreamento das despesas públicas desde a origem do crédito até sua execução. + columns: + - name: ne + description: > + Número da Nota de Empenho, extraído dos 12 últimos dígitos do campo `ne_ccor`, representando o identificador do empenho no SIAFI. + - name: num_transf + description: > + Número da transferência identificado na descrição da nota de empenho (`ne_ccor_descricao`), utilizado para vincular o empenho ao Plano de Ação correspondente. + - name: nc + description: > + Código da Nota de Crédito associado ao empenho, extraído da descrição da nota de empenho e formatado conforme padrão estabelecido. + - name: plano_acao + description: > + Identificador do Plano de Ação relacionado ao empenho, obtido a partir da correspondência com o número de transferência (`num_transf`). + - name: ne_ccor + description: > + Campo original contendo o código completo da Nota de Empenho, utilizado como base para extração de identificadores. + - name: ne_ccor_descricao + description: > + Descrição textual da Nota de Empenho, de onde são extraídos os números de transferência e códigos de nota de crédito para associação com os Planos de Ação. + - name: demais_colunas + description: > + Outras colunas provenientes da tabela `empenhos_tesouro`, contendo informações adicionais sobre os empenhos, como data de emissão, valor, unidade gestora, entre outras. - name: nc_plano_acao description: > - Dados das notas de crédito extraídas do SIAFI, incluso o identificador do plano de ação. - Essa tabela é atualizada diariamente. + Esta tabela estabelece a relação entre as Notas de Crédito registradas no SIAFI e os respectivos Planos de Ação, + permitindo o rastreamento das movimentações financeiras desde a origem do crédito até sua aplicação. + columns: + - name: plano_acao + description: > + Identificador do Plano de Ação relacionado à Nota de Crédito, obtido a partir da correspondência com o número de transferência (`nc_transferencia`). + - name: emissao_dia + description: > + Dia de emissão da Nota de Crédito. + - name: num_transf + description: > + Número da transferência associado à Nota de Crédito, utilizado para vincular a movimentação financeira ao Plano de Ação correspondente. + - name: nc + description: > + Código da Nota de Crédito, extraído dos 12 últimos dígitos do campo `nc`, representando o identificador da nota no SIAFI. + - name: nc_fonte_recursos + description: > + Código da fonte de recursos associada à Nota de Crédito, indicando a origem dos recursos utilizados. + - name: ptres + description: > + Código do Plano Interno de Trabalho (PTRES) vinculado à Nota de Crédito, representando a ação orçamentária correspondente. + - name: ug_emitente + description: > + Código da Unidade Gestora emitente da Nota de Crédito, extraído dos 6 primeiros dígitos do campo `nc`. + - name: nc_natureza_despesa + description: > + Código da natureza da despesa associada à Nota de Crédito, conforme classificação orçamentária. + - name: nc_evento + description: > + Código do evento contábil associado à Nota de Crédito, representando a natureza da transação. + - name: nc_evento_descr + description: > + Descrição do evento contábil vinculado à Nota de Crédito. + - name: nc_valor + description: > + Valor monetário da Nota de Crédito, ajustado conforme o tipo de evento contábil. - name: pf_plano_acao description: > - Dados das programações financeiras extraídas do SIAFI, includo o identificador do plano de ação. - Essa tabela é atualizada diariamente. + Esta tabela consolida as programações financeiras executadas no âmbito do SIAFI que estão relacionadas a Planos de Ação. + columns: + - name: pf + description: > + Número identificador da Programação Financeira, conforme registrado no SIAFI. + - name: num_transf + description: > + Número da transferência extraído do campo `pf_inscricao`, utilizado como chave alternativa para vinculação com Planos de Ação. + - name: emissao_mes + description: > + Mês da emissão da Programação Financeira, no formato numérico (1 a 12). + - name: emissao_dia + description: > + Dia da emissão da Programação Financeira, no formato numérico (1 a 31). + - name: ug_emitente + description: > + Código da Unidade Gestora responsável pela emissão da Programação Financeira. + - name: ug_favorecido + description: > + Código da Unidade Gestora favorecida pela Programação Financeira. + - name: pf_evento + description: > + Código do evento contábil associado à Programação Financeira, que descreve o tipo de movimentação registrada. + - name: pf_evento_descricao + description: > + Descrição do evento contábil da Programação Financeira, fornecendo contexto adicional sobre o tipo de operação realizada. + - name: pf_acao + description: > + Código da ação orçamentária extraído da descrição da programação financeira, representando a finalidade da despesa. + - name: pf_valor_linha + description: > + Valor programado para execução financeira, referente à linha específica da Programação Financeira. + - name: plano_acao + description: > + Identificador do Plano de Ação associado à Programação Financeira, determinado por correspondência direta com o sistema TransfereGov ou via número de transferência. + + - name: ted_resumo_orcamentario + description: > + Tabela de consolidação orçamentária e financeira por Plano de Ação, baseada em informações de valores firmados, orçamentos recebidos e devolvidos, empenhos, despesas e transferências financeiras. + Os dados são integrados de diferentes fontes (notas de crédito, empenhos e programações financeiras) para fornecer um resumo abrangente da execução orçamentária e financeira de transferências intergovernamentais. + columns: + - name: plano_acao + description: > + Identificador único do Plano de Ação associado aos registros orçamentários e financeiros. + - name: num_transf + description: > + Número da transferência utilizado como chave de ligação entre diferentes fontes de dados. + - name: sigla_unidade_descentralizada + description: > + Sigla da Unidade Descentralizada responsável pelo Plano de Ação. + - name: ted_beneficiario_emitente + description: > + Identifica se a Unidade do Plano de Ação é a beneficiária ou a emitente dos recursos da TED (Transferência Voluntária). Valores possíveis: 'beneficiario', 'emitente' ou 'nao_indicado'. + - name: valor_firmado + description: > + Valor total originalmente acordado no Plano de Ação como meta de transferência de recursos. + - name: orcamento_recebido + description: > + Total de créditos orçamentários efetivamente recebidos por meio de Notas de Crédito. + - name: orcamento_devolvido + description: > + Total de créditos orçamentários devolvidos, identificado por eventos contábeis específicos (ex.: 300301, 300307). + - name: empenhado + description: > + Soma dos valores empenhados, ou seja, das despesas formalizadas no orçamento, excluindo anulações. + - name: empenho_anulado + description: > + Total de valores de empenhos anulados, ou seja, revertidos após sua emissão. + - name: despesas_pagas_exercicio + description: > + Total de despesas pagas no exercício corrente. + - name: despesas_pagas_rap + description: > + Total de despesas pagas com recursos de Restos a Pagar. + - name: restos_a_pagar + description: > + Valor total inscrito como Restos a Pagar, ou seja, despesas empenhadas e não pagas até o fim do exercício. + - name: despesas_liquidada + description: > + Soma das despesas liquidadas, indicando bens ou serviços efetivamente entregues. + - name: financeiro_recebido + description: > + Total de recursos financeiros recebidos via Programação Financeira (TED), considerando apenas ações classificadas como 'TRANSFERENCIA'. + - name: financeiro_devolvido + description: > + Total de recursos financeiros devolvidos, com ações classificadas como 'DEVOLUCAO'. + - name: financeiro_cancelado + description: > + Valor total de cancelamentos financeiros identificados na programação, com ação 'CANCELAMENTO'. + + - name: num_transf_n_plano_acao + description: > + View responsável por mapear o número de transferência (`num_transf`) ao respectivo Plano de Ação (`plano_acao`). + Utiliza dados de Notas de Crédito do TransfereGov e do SIAFI para realizar o cruzamento entre diferentes sistemas. + columns: + - name: num_transf + description: > + Número da transferência financeira obtido a partir dos registros do SIAFI (nota de crédito). Serve como chave para integrar informações entre diferentes fontes, como notas de crédito, programações financeiras e empenhos. + - name: plano_acao + description: > + Identificador único do Plano de Ação, conforme registrado no TransfereGov. É atribuído ao `num_transf` com base no cruzamento entre a nota de crédito (NC) e a unidade gestora emitente (UG). macros: - name: create_udfs From 5aa1e4d6e7aabd542fb750bd2f8a24985c34c902 Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Wed, 21 May 2025 17:36:12 +0000 Subject: [PATCH 103/317] Feat/dag siorg --- .../data_ingest/cargos_funcao_ingest_dag.py | 54 +++++++++++ ...rutura_organizacional_cargos_ingest_dag.py | 60 +++++++++++++ .../unidade_organizacional_ingest_dag.py | 4 +- airflow_lappis/plugins/cliente_estrutura.py | 45 ---------- airflow_lappis/plugins/cliente_postgres.py | 13 +++ airflow_lappis/plugins/cliente_siorg.py | 90 +++++++++++++++++++ 6 files changed, 219 insertions(+), 47 deletions(-) create mode 100644 airflow_lappis/dags/data_ingest/cargos_funcao_ingest_dag.py create mode 100644 airflow_lappis/dags/data_ingest/estrutura_organizacional_cargos_ingest_dag.py delete mode 100755 airflow_lappis/plugins/cliente_estrutura.py create mode 100755 airflow_lappis/plugins/cliente_siorg.py diff --git a/airflow_lappis/dags/data_ingest/cargos_funcao_ingest_dag.py b/airflow_lappis/dags/data_ingest/cargos_funcao_ingest_dag.py new file mode 100644 index 00000000..1f72f873 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/cargos_funcao_ingest_dag.py @@ -0,0 +1,54 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from postgres_helpers import get_postgres_conn +from cliente_siorg import ClienteSiorg +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "retries": 1, + "retry_delay": timedelta(minutes=5), + "owner": "Davi", + }, + tags=["estrutura_organizacional", "siorg"], +) +def api_cargos_funcao_dag() -> None: + @task + def fetch_and_store_cargos_funcao() -> None: + api = ClienteSiorg() + db = ClientPostgresDB(get_postgres_conn()) + try: + cargos_funcao_data = api.get_cargos_funcao() + if not cargos_funcao_data: + logging.warning("Nenhum dado retornado pela API de cargos/função.") + return + + registros = [] + for tipo in cargos_funcao_data: + tipo_base = {k: v for k, v in tipo.items() if k != "cargosFuncoes"} + if "cargosFuncoes" in tipo and "cargoFuncao" in tipo["cargosFuncoes"]: + for cargo in tipo["cargosFuncoes"]["cargoFuncao"]: + registro = {**tipo_base, **cargo} + registros.append(registro) + if registros: + db.insert_data( + registros, + "cargos_funcao", + conflict_fields=["codigoCargoFuncao"], + primary_key=["codigoCargoFuncao"], + schema="siorg", + ) + else: + logging.warning("Nenhum cargo/função encontrado para inserir.") + except Exception as e: + logging.error(f"Erro ao buscar/inserir cargos função: {e}") + + fetch_and_store_cargos_funcao() + + +dag_instance = api_cargos_funcao_dag() diff --git a/airflow_lappis/dags/data_ingest/estrutura_organizacional_cargos_ingest_dag.py b/airflow_lappis/dags/data_ingest/estrutura_organizacional_cargos_ingest_dag.py new file mode 100644 index 00000000..4de00bd2 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/estrutura_organizacional_cargos_ingest_dag.py @@ -0,0 +1,60 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from postgres_helpers import get_postgres_conn +from cliente_siorg import ClienteSiorg +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={"retries": 1, "retry_delay": timedelta(minutes=5), "owner": "Davi"}, + tags=["estrutura_organizacional", "siorg"], +) +def api_estrutura_organizacional_cargos_dag() -> None: + """Busca dados da estrutura organizacional via API e armazena no PostgreSQL.""" + + @task + def fetch_estrutura_organizacional_cargos() -> None: + try: + api = ClienteSiorg() + db = ClientPostgresDB(get_postgres_conn()) + codigo_unidades = db.get_codigo_unidade() + + if not codigo_unidades: + logging.warning("Nenhum código de unidade encontrado.") + return + + for codigo_unidade in codigo_unidades: + try: + estrutura_cargos = api.get_estrutura_organizacional_cargos( + codigo_unidade + ) + + if estrutura_cargos: + db.insert_data( + [estrutura_cargos], + "estrutura_organizacional_cargos", + conflict_fields=["codigoUnidade"], + primary_key=["codigoUnidade"], + schema="siorg", + ) + else: + logging.debug(f"Sem dados para codigoUnidade {codigo_unidade}.") + + except Exception as e: + logging.error( + f"Erro ao processar codigoUnidade {codigo_unidade}: {e}", + exc_info=True, + ) + + except Exception as e: + logging.error(f"Erro geral na tarefa: {e}", exc_info=True) + raise + + fetch_estrutura_organizacional_cargos() + + +dag_instance = api_estrutura_organizacional_cargos_dag() diff --git a/airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py b/airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py index 498c337b..3022c91c 100755 --- a/airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py @@ -2,7 +2,7 @@ from airflow.decorators import dag, task from datetime import datetime, timedelta from postgres_helpers import get_postgres_conn -from cliente_estrutura import ClienteEstrutura +from cliente_siorg import ClienteSiorg from cliente_postgres import ClientPostgresDB @@ -26,7 +26,7 @@ def fetch_estrutura_organizacional_resumida() -> None: "[unidade_organizacional_ingest_dag.py] " "Starting fetch_estrutura_organizacional_resumida task" ) - api = ClienteEstrutura() + api = ClienteSiorg() postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) diff --git a/airflow_lappis/plugins/cliente_estrutura.py b/airflow_lappis/plugins/cliente_estrutura.py deleted file mode 100755 index 7522b657..00000000 --- a/airflow_lappis/plugins/cliente_estrutura.py +++ /dev/null @@ -1,45 +0,0 @@ -import http -from typing import Optional - -from cliente_base import ClienteBase - - -class ClienteEstrutura(ClienteBase): - - BASE_URL = "https://estruturaorganizacional.dados.gov.br/doc" - - def __init__(self) -> None: - super().__init__(base_url=ClienteEstrutura.BASE_URL) - - def get_estrutura_organizacional_resumida( - self, - codigo_poder: Optional[str] = None, - codigo_esfera: Optional[str] = None, - codigo_unidade: Optional[str] = None, - ) -> Optional[list]: - """ - Consultar Estrutura Organizacional Resumida. - - Args: - codigo_poder (Optional[str]): código do poder - codigo_esfera (Optional[str]): código da esfera - codigo_unidade (Optional[str]): código da unidade - - Returns: - dict: Estrutura Organizacional Resumida. - """ - endpoint = "/estrutura-organizacional/resumida" - params = {} - if codigo_poder: - params["codigoPoder"] = codigo_poder - if codigo_esfera: - params["codigoEsfera"] = codigo_esfera - if codigo_unidade: - params["codigoUnidade"] = codigo_unidade - - status, data = self.request(http.HTTPMethod.GET, endpoint, params=params) - return ( - data.get("unidades", []) - if status == http.HTTPStatus.OK and type(data) is dict - else None - ) diff --git a/airflow_lappis/plugins/cliente_postgres.py b/airflow_lappis/plugins/cliente_postgres.py index 9da79a47..7687938a 100755 --- a/airflow_lappis/plugins/cliente_postgres.py +++ b/airflow_lappis/plugins/cliente_postgres.py @@ -377,3 +377,16 @@ def remove_duplicates( f"Erro ao remover duplicados ou otimizar {schema}.{table_name}: {str(e)}" ) raise + + def get_codigo_unidade(self) -> list[int]: + """Retorna o código da unidade da tabela unidade_organizacional.""" + query = ( + "SELECT regexp_replace(codigounidade, '.*/', '') " + "FROM siorg.unidade_organizacional" + ) + + with psycopg2.connect(self.conn_str) as conn: + with conn.cursor() as cursor: + cursor.execute(query) + codigo_unidade = [int(row[0]) for row in cursor.fetchall()] + return codigo_unidade diff --git a/airflow_lappis/plugins/cliente_siorg.py b/airflow_lappis/plugins/cliente_siorg.py new file mode 100755 index 00000000..dfa36e25 --- /dev/null +++ b/airflow_lappis/plugins/cliente_siorg.py @@ -0,0 +1,90 @@ +import http +from typing import Optional + +from cliente_base import ClienteBase + + +class ClienteSiorg(ClienteBase): + + BASE_URL = "https://estruturaorganizacional.dados.gov.br/doc" + + def __init__(self) -> None: + super().__init__(base_url=ClienteSiorg.BASE_URL) + + def get_estrutura_organizacional_resumida( + self, + codigo_poder: Optional[str] = None, + codigo_esfera: Optional[str] = None, + codigo_unidade: Optional[str] = None, + ) -> Optional[list]: + """ + Consultar Estrutura Organizacional Resumida. + + Args: + codigo_poder (Optional[str]): código do poder + codigo_esfera (Optional[str]): código da esfera + codigo_unidade (Optional[str]): código da unidade + + Returns: + dict: Estrutura Organizacional Resumida. + """ + endpoint = "/estrutura-organizacional/resumida" + params = {} + if codigo_poder: + params["codigoPoder"] = codigo_poder + if codigo_esfera: + params["codigoEsfera"] = codigo_esfera + if codigo_unidade: + params["codigoUnidade"] = codigo_unidade + + status, data = self.request(http.HTTPMethod.GET, endpoint, params=params) + return ( + data.get("unidades", []) + if status == http.HTTPStatus.OK and type(data) is dict + else None + ) + + def get_estrutura_organizacional_cargos( + self, + codigo_unidade: Optional[str] = None, + ) -> Optional[dict]: + """ + Consultar Estrutura Organizacional Cargos. + + Args: + codigo_unidade (Optional[str]): código da unidade + + Returns: + dict: Estrutura Organizacional Cargos. + """ + endpoint = "/instancias/consulta-unidade" + params = {} + if codigo_unidade: + params["codigoUnidade"] = codigo_unidade + + headers = {"accept": "*/*"} + + status, data = self.request( + http.HTTPMethod.GET, endpoint, params=params, headers=headers + ) + return ( + data.get("unidade", []) + if status == http.HTTPStatus.OK and type(data) is dict + else None + ) + + def get_cargos_funcao(self) -> Optional[dict]: + """ + Consultar Cargos Função. + + Returns: + dict: Cargos Função. + """ + endpoint = "/cargo-funcao" + + status, data = self.request(http.HTTPMethod.GET, endpoint) + return ( + data.get("tipoCargoFuncao", []) + if status == http.HTTPStatus.OK and type(data) is dict + else None + ) From 2ae554da00cf72c76740f84c99bf27026a53af1e Mon Sep 17 00:00:00 2001 From: joao Date: Thu, 22 May 2025 12:31:21 -0300 Subject: [PATCH 104/317] feat: adicionando siape --- airflow_lappis/dags/dbt/ipea/dbt_project.yml | 5 +++++ airflow_lappis/dags/dbt/ipea/models/schema.yml | 15 +++++++++++++++ .../dbt/ipea/models/siape_dbt/bronze/uorgs.sql | 5 +++++ airflow_lappis/dags/dbt/ipea/models/sources.yml | 4 ++++ 4 files changed, 29 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/uorgs.sql diff --git a/airflow_lappis/dags/dbt/ipea/dbt_project.yml b/airflow_lappis/dags/dbt/ipea/dbt_project.yml index 479b5393..9fe908fe 100755 --- a/airflow_lappis/dags/dbt/ipea/dbt_project.yml +++ b/airflow_lappis/dags/dbt/ipea/dbt_project.yml @@ -37,6 +37,11 @@ models: +schema: ted views: +materialized: view + siape_dbt: + +materialized: table + +schema: siape + views: + +materialized: view on-run-start: - '{{create_udfs()}}' \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/ipea/models/schema.yml b/airflow_lappis/dags/dbt/ipea/models/schema.yml index 6bf8ac32..8dbc1b5a 100644 --- a/airflow_lappis/dags/dbt/ipea/models/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/schema.yml @@ -942,6 +942,21 @@ models: description: > Número da nota de empenho relacionada ao contrato, combinando informações de contratos, empenhos e faturas. + # SIAPE DBT + ## Bronze + - name: uorgs + description: > + Modelo bronze para dados de uorg. Inclui limpeza básica, tipagem, transformação de datas + columns: + - name: codigo + description: > + codigo da uorg + - name: dataultimatransacao + description: > + data da última transação + - name: nome + description: > + data da última transação # Pessoas DBT diff --git a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/uorgs.sql b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/uorgs.sql new file mode 100644 index 00000000..b1111c65 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/uorgs.sql @@ -0,0 +1,5 @@ +SELECT + cast(codigo AS INT) AS codigo, + dataultimatransacao, + nome +FROM {{ source('siape', 'lista_uorgs') }} \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/ipea/models/sources.yml b/airflow_lappis/dags/dbt/ipea/models/sources.yml index 8ba84b9c..05a61c4d 100644 --- a/airflow_lappis/dags/dbt/ipea/models/sources.yml +++ b/airflow_lappis/dags/dbt/ipea/models/sources.yml @@ -24,4 +24,8 @@ sources: - name: planos_acao - name: programacao_financeira - name: programas + - name: siape + schema: siape + tables: + - name: lista_uorgs From 07ef73af4b639e0fe22019fbad731c303cedcb4e Mon Sep 17 00:00:00 2001 From: joao Date: Thu, 22 May 2025 15:29:40 -0300 Subject: [PATCH 105/317] created uorgs table at bronze tenant --- .../dags/dbt/ipea/models/siape_dbt/bronze/uorgs.sql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/uorgs.sql b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/uorgs.sql index b1111c65..0f208f0f 100644 --- a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/uorgs.sql +++ b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/uorgs.sql @@ -1,5 +1,7 @@ + + SELECT cast(codigo AS INT) AS codigo, - dataultimatransacao, + TO_DATE(dataultimatransacao, 'DDMMYYYY') AS dataultimatransacao, nome FROM {{ source('siape', 'lista_uorgs') }} \ No newline at end of file From 9fb786a838658230604c3c5df287beb069f2dff4 Mon Sep 17 00:00:00 2001 From: joao Date: Thu, 22 May 2025 18:16:39 -0300 Subject: [PATCH 106/317] created servidores and dados_uorg tables at siape bronze tenant --- .../dags/dbt/ipea/models/schema.yml | 139 +++++++++++++++++- .../models/siape_dbt/bronze/dados_uorg.sql | 44 ++++++ .../siape_dbt/bronze/lista_servidores.sql | 9 ++ .../models/siape_dbt/bronze/lista_uorgs.sql | 10 ++ .../ipea/models/siape_dbt/bronze/uorgs.sql | 7 - .../dags/dbt/ipea/models/sources.yml | 2 + 6 files changed, 202 insertions(+), 9 deletions(-) create mode 100644 airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_uorg.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/lista_servidores.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/lista_uorgs.sql delete mode 100644 airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/uorgs.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/schema.yml b/airflow_lappis/dags/dbt/ipea/models/schema.yml index 8dbc1b5a..6912fe7a 100644 --- a/airflow_lappis/dags/dbt/ipea/models/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/schema.yml @@ -944,19 +944,154 @@ models: # SIAPE DBT ## Bronze - - name: uorgs + - name: lista_uorgs description: > Modelo bronze para dados de uorg. Inclui limpeza básica, tipagem, transformação de datas columns: - name: codigo description: > codigo da uorg - - name: dataultimatransacao + - name: dt_ultima_transacao description: > data da última transação - name: nome + description: > + nome + + - name: lista_servidores + description: > + Modelo bronze para dados de servidores. Inclui limpeza básica, tipagem, transformação de datas + columns: + - name: cod_uorg + description: > + codigo da uorg + - name: dt_ultima_transacao description: > data da última transação + - name: cpf + description: > + registro pessoa física + + - name: dados_uorg + description: > + Tabela bronze contendo dados de Unidades Organizacionais (UORG), + após limpeza básica, padronização de tipos e formatos a partir da fonte bruta. + Esta tabela serve como uma representação fiel e limpa dos dados de origem. + columns: + - name: bairro_uorg + description: > + Bairro do endereço da UORG. + data_type: varchar + + - name: cep_uorg + description: > + CEP (Código de Endereçamento Postal) da UORG, + contendo apenas dígitos (limpo). + data_type: varchar(8) + # tests: + # - dbt_utils.equal_length: + # value: 8 + # config: + # severity: warn + + - name: codigo_matricula + description: > + Código de matrícula associado à UORG. + Pode conter zeros à esquerda ou ser alfanumérico. + data_type: varchar + # tests: + # - not_null + + - name: codigo_municipio_uorg + description: > + Código do município da UORG. + data_type: varchar + + - name: codigo_orgao + description: > + Código do órgão ao qual a UORG pertence. + data_type: varchar + + - name: codigo_orgao_uorg + description: > + Código identificador único da UORG dentro do órgão. + data_type: varchar + # tests: + # - not_null + # - unique + + - name: email_uorg + description: > + Endereço de e-mail da UORG, padronizado para minúsculas. + data_type: varchar + # tests: + # - dbt_utils.expression_is_true: + # expression: > # Exemplo de como aplicar em expressões também + # email_uorg LIKE '%@%.%' + # config: + # severity: warn + + - name: tipo_endereco_uorg + description: > + Tipo ou descrição principal do endereço da UORG (ex: ENDERECO PRINCIPAL). + data_type: varchar + + - name: logradouro_uorg + description: > + Logradouro (rua, avenida, etc.) do endereço da UORG. + data_type: varchar + + - name: nome_municipio_uorg + description: > + Nome do município onde a UORG está localizada. + data_type: varchar + + - name: nome_uorg + description: > + Nome completo da Unidade Organizacional (UORG). + data_type: varchar + tests: + - not_null + + - name: telefone_uorg + description: > + Número de telefone da UORG, contendo apenas dígitos (limpo). + data_type: varchar + + - name: numero_endereco_uorg + description: > + Número do endereço da UORG. Pode conter 'S/N' para 'Sem Número'. + data_type: varchar + + - name: sigla_uorg + description: > + Sigla ou nome curto da UORG. + data_type: varchar + + - name: uf_uorg + description: > + Sigla da Unidade Federativa (Estado) da UORG, padronizada para maiúsculas. + data_type: varchar(2) + + - name: cpf + description: > + CPF associado à UORG, contendo apenas os 11 dígitos (limpo). + data_type: varchar(11) + # tests: + # - dbt_utils.equal_length: + # value: 11 + # config: + # severity: warn + + - name: complemento_endereco_uorg + description: > + Complemento do endereço da UORG (ex: Sala, Bloco, Apartamento). + data_type: varchar + + - name: fax_uorg + description: > + Número de fax da UORG, contendo apenas dígitos (limpo). + data_type: varchar # Pessoas DBT diff --git a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_uorg.sql b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_uorg.sql new file mode 100644 index 00000000..6deadbfa --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_uorg.sql @@ -0,0 +1,44 @@ +WITH dados_uorg AS ( + SELECT + bairrouorg, + cepuorg, + codmatricula, + codmunicipiouorg, + codorgao, + codorgaouorg, + emailuorg, + enduorg, + logradourouorg, + nomemunicipiouorg, + nomeuorg, + numtelefoneuorg, + numerouorg, + siglauorg, + ufuorg, + cpf, + complementouorg, + numfaxuorg + FROM {{ source('siape', 'dados_uorg') }} +) + +SELECT + NULLIF(TRIM(bairrouorg), '') AS bairro_uorg, + REGEXP_REPLACE(NULLIF(TRIM(cepuorg), ''), '[^0-9]', '', 'g') AS cep_uorg, + NULLIF(TRIM(codmatricula), '') AS codigo_matricula, + NULLIF(TRIM(codmunicipiouorg), '') AS codigo_municipio_uorg, + NULLIF(TRIM(codorgao), '') AS codigo_orgao, + NULLIF(TRIM(codorgaouorg), '') AS codigo_orgao_u + LOWER(NULLIF(TRIM(emailuorg), '')) AS email_uorg, + NULLIF(TRIM(enduorg), '') AS tipo_endereco_uorg, + NULLIF(TRIM(logradourouorg), '') AS logradouro_uorg, + NULLIF(TRIM(nomemunicipiouorg), '') AS nome_municipio_uorg, + NULLIF(TRIM(nomeuorg), '') AS nome_uorg, + REGEXP_REPLACE(NULLIF(TRIM(numtelefoneuorg), ''), '[^0-9]', '', 'g') AS telefone_uorg, + NULLIF(TRIM(numerouorg), '') AS numero_endereco_uorg, + NULLIF(TRIM(siglauorg), '') AS sigla_uorg, + UPPER(NULLIF(TRIM(ufuorg), '')) AS uf_uorg, + REGEXP_REPLACE(NULLIF(TRIM(cpf), ''), '[^0-9]', '', 'g') AS cpf, + NULLIF(NULLIF(TRIM(complementouorg), ''), '---') AS complemento_endereco_uorg, + REGEXP_REPLACE(NULLIF(TRIM(numfaxuorg), ''), '[^0-9]', '', 'g') AS fax_uorg +FROM + dados_uorg diff --git a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/lista_servidores.sql b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/lista_servidores.sql new file mode 100644 index 00000000..0ef1975e --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/lista_servidores.sql @@ -0,0 +1,9 @@ +WITH lista_servidores AS ( + SELECT + cpf, + TO_DATE(dataultimatransacao, 'DDMMYYYY') AS dt_ultima_transacao, + coduorg AS cod_uorg + FROM {{ source('siape', 'lista_servidores') }} +) + +SELECT * FROM lista_servidores diff --git a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/lista_uorgs.sql b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/lista_uorgs.sql new file mode 100644 index 00000000..6758e481 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/lista_uorgs.sql @@ -0,0 +1,10 @@ + +WITH lista_uorgs AS ( + SELECT + cast(codigo AS INT) AS codigo, + TO_DATE(dataultimatransacao, 'DDMMYYYY') AS dt_ultima_transacao, + nome + FROM {{ source('siape', 'lista_uorgs') }} +) + +SELECT * FROM lista_uorgs diff --git a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/uorgs.sql b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/uorgs.sql deleted file mode 100644 index 0f208f0f..00000000 --- a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/uorgs.sql +++ /dev/null @@ -1,7 +0,0 @@ - - -SELECT - cast(codigo AS INT) AS codigo, - TO_DATE(dataultimatransacao, 'DDMMYYYY') AS dataultimatransacao, - nome -FROM {{ source('siape', 'lista_uorgs') }} \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/ipea/models/sources.yml b/airflow_lappis/dags/dbt/ipea/models/sources.yml index 05a61c4d..1e856586 100644 --- a/airflow_lappis/dags/dbt/ipea/models/sources.yml +++ b/airflow_lappis/dags/dbt/ipea/models/sources.yml @@ -28,4 +28,6 @@ sources: schema: siape tables: - name: lista_uorgs + - name: lista_servidores + - name: dados_uorg From 3d90f4a3b6f89bcf8757593382b1534608ce8620 Mon Sep 17 00:00:00 2001 From: joao Date: Thu, 22 May 2025 19:29:17 -0300 Subject: [PATCH 107/317] created dados_pessoais table at siape bronze tenant --- .../dags/dbt/ipea/models/schema.yml | 127 ++++++++++++++++++ .../siape_dbt/bronze/dados_pessoais.sql | 52 +++++++ .../dags/dbt/ipea/models/sources.yml | 1 + 3 files changed, 180 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_pessoais.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/schema.yml b/airflow_lappis/dags/dbt/ipea/models/schema.yml index 6912fe7a..472c4ff5 100644 --- a/airflow_lappis/dags/dbt/ipea/models/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/schema.yml @@ -1093,6 +1093,133 @@ models: Número de fax da UORG, contendo apenas dígitos (limpo). data_type: varchar + - name: dados_pessoais + description: > + Tabela bronze contendo dados pessoais de indivíduos, após limpeza básica, + padronização de tipos e formatos a partir da fonte bruta. + columns: + - name: cod_cor + description: > + Código da cor/raça do indivíduo. + data_type: varchar + # tests: + # - not_null + + - name: cod_estado_civil + description: > + Código do estado civil do indivíduo. + data_type: varchar + + - name: cod_nacionalidade + description: > + Código da nacionalidade do indivíduo. + data_type: varchar + + - name: cod_sexo + description: > + Código do sexo do indivíduo (ex: M, F). + data_type: varchar(1) + + - name: dt_nascimento + description: > + Data de nascimento do indivíduo, convertida para o tipo DATE. + data_type: date + # tests: + # - not_null + + - name: grupo_sanguineo + description: > + Grupo sanguíneo e fator RH do indivíduo (ex: A +, O -). + data_type: varchar + + - name: nome_pessoa + description: > + Nome completo do indivíduo. + data_type: varchar + # tests: + # - not_null + + - name: nome_cor + description: > + Nome descritivo da cor/raça do indivíduo. + data_type: varchar + + - name: nome_estado_civil + description: > + Nome descritivo do estado civil do indivíduo. + data_type: varchar + + - name: nome_mae + description: > + Nome completo da mãe do indivíduo. + data_type: varchar + + - name: nome_municipio_nascimento + description: > + Nome do município de nascimento do indivíduo. + data_type: varchar + + - name: nome_nacionalidade + description: > + Nome descritivo da nacionalidade do indivíduo (ex: BRASILEIRO NATO). + data_type: varchar + + - name: nome_pai + description: > + Nome completo do pai do indivíduo. 'NAO DECLARADO' é convertido para NULL. + data_type: varchar + + - name: nome_sexo + description: > + Nome descritivo do sexo do indivíduo (ex: MASCULINO, FEMININO). + data_type: varchar + + - name: num_pispasep + description: > + Número do PIS/PASEP do indivíduo, contendo apenas dígitos. + data_type: varchar # Para preservar zeros à esquerda e pelo tamanho + # tests: + # - dbt_utils.equal_length: + # value: 11 # PIS/PASEP tem 11 dígitos + + - name: uf_nascimento + description: > + Sigla da Unidade Federativa (Estado) de nascimento, padronizada para maiúsculas. + data_type: varchar(2) + # tests: + # - dbt_utils.equal_length: + # value: 2 + + - name: cpf + description: > + CPF do indivíduo, contendo apenas os 11 dígitos. + data_type: varchar(11) + # tests: + # - not_null + # - unique + # - dbt_utils.equal_length: + # value: 11 + + - name: cod_deficiencia_fisica + description: > + Código da deficiência física, se houver. + data_type: varchar + + - name: nome_deficiencia_fisica + description: > + Nome da deficiência física, se houver. + data_type: varchar + + - name: dt_chegada_brasil + description: > + Data de chegada ao Brasil (para estrangeiros), convertida para o tipo DATE. + data_type: date + + - name: nome_pais_origem + description: > + Nome do país de origem (para estrangeiros). + data_type: varchar + # Pessoas DBT # TED DBT diff --git a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_pessoais.sql b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_pessoais.sql new file mode 100644 index 00000000..68599975 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_pessoais.sql @@ -0,0 +1,52 @@ + + +WITH dados_pessoais AS ( + SELECT + codcor, + codestadocivil, + codnacionalidade, + codsexo, + datanascimento, + gruposanguineo, + nome, + nomecor, + nomeestadocivil, + nomemae, + nomemunicipnasc, + nomenacionalidade, + nomepai, + nomesexo, + numpispasep, + ufnascimento, + cpf, + coddeffisica, + nomedeffisica, + datachegbrasil, + nomepais + FROM {{ source('siape', 'dados_pessoais') }} +) + +SELECT + NULLIF(TRIM(codcor), '') AS cod_cor, + NULLIF(TRIM(codestadocivil), '') AS cod_estado_civil, + NULLIF(TRIM(codnacionalidade), '') AS cod_nacionalidade, + NULLIF(TRIM(codsexo), '') AS cod_sexo, + TO_DATE(NULLIF(TRIM(datanascimento), ''), 'DDMMYYYY') AS dt_nascimento, + NULLIF(TRIM(gruposanguineo), '') AS grupo_sanguineo, + NULLIF(TRIM(nome), '') AS nome_pessoa, + NULLIF(TRIM(nomecor), '') AS nome_cor, + NULLIF(TRIM(nomeestadocivil), '') AS nome_estado_civil, + NULLIF(TRIM(nomemae), '') AS nome_mae, + NULLIF(TRIM(nomemunicipnasc), '') AS nome_municipio_nascimento, + NULLIF(TRIM(nomenacionalidade), '') AS nome_nacionalidade, + NULLIF(NULLIF(TRIM(nomepai), ''), 'NAO DECLARADO') AS nome_pai, + NULLIF(TRIM(nomesexo), '') AS nome_sexo, + REGEXP_REPLACE(NULLIF(TRIM(numpispasep), ''), '[^0-9]', '', 'g') AS num_pispasep, + UPPER(NULLIF(TRIM(ufnascimento), '')) AS uf_nascimento, + REGEXP_REPLACE(NULLIF(TRIM(cpf), ''), '[^0-9]', '', 'g') AS cpf, + NULLIF(TRIM(coddeffisica), '') AS cod_deficiencia_fisica, + NULLIF(TRIM(nomedeffisica), '') AS nome_deficiencia_fisica, + TO_DATE(NULLIF(TRIM(datachegbrasil), ''), 'DDMMYYYY') AS dt_chegada_brasil, + NULLIF(TRIM(nomepais), '') AS nome_pais_origem +FROM + dados_pessoais \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/ipea/models/sources.yml b/airflow_lappis/dags/dbt/ipea/models/sources.yml index 1e856586..cfe5dd20 100644 --- a/airflow_lappis/dags/dbt/ipea/models/sources.yml +++ b/airflow_lappis/dags/dbt/ipea/models/sources.yml @@ -30,4 +30,5 @@ sources: - name: lista_uorgs - name: lista_servidores - name: dados_uorg + - name: dados_pessoais From 61a6b445591a7dd8df167b2aa950ba4466f3ea57 Mon Sep 17 00:00:00 2001 From: joao Date: Thu, 22 May 2025 19:48:39 -0300 Subject: [PATCH 108/317] created dados_pa table at siape bronze tenant --- .../dags/dbt/ipea/models/schema.yml | 85 +++++++++++++++++++ .../ipea/models/siape_dbt/bronze/dados_pa.sql | 34 ++++++++ .../dags/dbt/ipea/models/sources.yml | 1 + 3 files changed, 120 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_pa.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/schema.yml b/airflow_lappis/dags/dbt/ipea/models/schema.yml index 472c4ff5..9b15d42b 100644 --- a/airflow_lappis/dags/dbt/ipea/models/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/schema.yml @@ -1219,6 +1219,91 @@ models: description: > Nome do país de origem (para estrangeiros). data_type: varchar + + - name: dados_pa + description: > + Tabela bronze contendo dados de beneficiários de pensões e informações relacionadas, + após limpeza básica e padronização de formatos a partir da fonte bruta. + + columns: + - name: agencia_beneficiario + description: > + Código da agência bancária do beneficiário, contendo apenas dígitos. + data_type: varchar + # tests: + # - not_null + + - name: banco_beneficiario + description: > + Código do banco do beneficiário. + data_type: varchar + # tests: + # - not_null + + - name: cod_orgao + description: > + Código do órgão pagador. + data_type: varchar + # tests: + # - not_null + + - name: conta_beneficiario + description: > + Número da conta bancária do beneficiário, limpo e padronizado para maiúsculas + data_type: varchar + # tests: + # - not_null + + - name: cpf_beneficiario + description: > + CPF do beneficiário, contendo apenas dígitos. + Pode ter comprimento variável se a fonte não for padronizada para 11 dígitos. + data_type: varchar + # tests: + # - not_null + + - name: matricula_servidor + description: > + Matrícula do servidor ao qual a pensão está vinculada. + data_type: varchar + # tests: + # - not_null + + - name: nome_beneficiario + description: > + Nome completo do beneficiário da pensão. + data_type: varchar + # tests: + # - not_null + + - name: valor_ultima_pensao + description: > + Valor da última pensão registrada, como string. + data_type: varchar + + - name: cpf_servidor + description: > + CPF do servidor ao qual a pensão está vinculada, contendo apenas os 11 dígitos. + data_type: varchar(11) + # tests: + # - not_null + # - dbt_utils.equal_length: + # value: 11 + + - name: cod_vinculo_servidor + description: > + Código do tipo de vínculo do alimentado com o servidor. + data_type: varchar + + - name: nome_alimentado + description: > + Nome da pessoa alimentada (dependente/beneficiário da pensão, se diferente do beneficiário principal). + data_type: varchar + + - name: nome_vinculo_servidor + description: > + Nome descritivo do tipo de vínculo do alimentado com o servidor (ex: FILHO(A), EX-CONJUGE). + data_type: varchar # Pessoas DBT diff --git a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_pa.sql b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_pa.sql new file mode 100644 index 00000000..ea056ae8 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_pa.sql @@ -0,0 +1,34 @@ + + +WITH dados_pa AS ( + SELECT + agenciabeneficiario, + bancobeneficiario, + codorgao, + contabeneficiario, + cpfbeneficiario, + matricula, + nomebeneficiario, + valorultimapensao, + cpf, + codvinculoservidor, + nomealimentado, + nomevinculoservidor + FROM {{ source('siape', 'dados_pa') }} +) + +SELECT + REGEXP_REPLACE(NULLIF(TRIM(agenciabeneficiario), ''), '[^0-9]', '', 'g') AS agencia_beneficiario, + NULLIF(TRIM(bancobeneficiario), '') AS banco_beneficiario, + NULLIF(TRIM(codorgao), '') AS cod_orgao, + UPPER(REGEXP_REPLACE(NULLIF(TRIM(contabeneficiario), ''), '[^0-9A-Za-z]', '', 'g')) AS conta_beneficiario, + REGEXP_REPLACE(NULLIF(TRIM(cpfbeneficiario), ''), '[^0-9]', '', 'g') AS cpf_beneficiario, + NULLIF(TRIM(matricula), '') AS matricula_servidor, + NULLIF(TRIM(nomebeneficiario), '') AS nome_beneficiario, + NULLIF(TRIM(valorultimapensao), '') AS valor_ultima_pensao, + REGEXP_REPLACE(NULLIF(TRIM(cpf), ''), '[^0-9]', '', 'g') AS cpf_servidor, + NULLIF(TRIM(codvinculoservidor), '') AS cod_vinculo_servidor, + NULLIF(TRIM(nomealimentado), '') AS nome_alimentado, + NULLIF(TRIM(nomevinculoservidor), '') AS nome_vinculo_servidor +FROM + dados_pa \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/ipea/models/sources.yml b/airflow_lappis/dags/dbt/ipea/models/sources.yml index cfe5dd20..60574b03 100644 --- a/airflow_lappis/dags/dbt/ipea/models/sources.yml +++ b/airflow_lappis/dags/dbt/ipea/models/sources.yml @@ -31,4 +31,5 @@ sources: - name: lista_servidores - name: dados_uorg - name: dados_pessoais + - name: dados_pa From 1b5f76ea1f7120cb8e277320f3d6a4d3ef766cfb Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Fri, 23 May 2025 11:10:46 +0000 Subject: [PATCH 109/317] Feat/dbt pessoas estrutura organizacional cargos --- .../estrutura_organizacional_cargos.sql | 45 +++++++++++++++++++ .../dags/dbt/ipea/models/sources.yml | 6 +++ 2 files changed, 51 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/estrutura_organizacional_cargos.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/estrutura_organizacional_cargos.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/estrutura_organizacional_cargos.sql new file mode 100644 index 00000000..227d456c --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/estrutura_organizacional_cargos.sql @@ -0,0 +1,45 @@ +with + fonte as ( + select codigounidade, nomeunidade, siglaunidade, municipio, uf, cargos + from {{ source("siorg", "estrutura_organizacional_cargos") }} + ), + + cargos_expandidos as ( + select + f.codigounidade, f.nomeunidade, f.siglaunidade, f.municipio, f.uf, cargo_elem + from + fonte f, + lateral jsonb_array_elements( + replace(replace(f.cargos, '''', '"'), 'None', 'null')::jsonb + ) as cargo_elem + ), + + instancias_expandidas as ( + select + ce.codigounidade, + ce.nomeunidade, + ce.siglaunidade, + ce.municipio, + ce.uf, + cargo_elem ->> 'denominacao' as denominacao, + cargo_elem ->> 'funcao' as funcao, + instancia_elem ->> 'codigoInstancia' as codigo_instancia, + instancia_elem ->> 'nomeTitular' as nome_titular, + instancia_elem ->> 'cpfTitular' as cpf_titular + from + cargos_expandidos ce, + lateral jsonb_array_elements(cargo_elem -> 'instancias') as instancia_elem + ) + +select + codigounidade, + nomeunidade, + siglaunidade, + municipio, + uf, + denominacao, + funcao, + codigo_instancia::bigint as codigo_instancia, + nome_titular, + cpf_titular +from instancias_expandidas diff --git a/airflow_lappis/dags/dbt/ipea/models/sources.yml b/airflow_lappis/dags/dbt/ipea/models/sources.yml index 8ba84b9c..e5642fbc 100644 --- a/airflow_lappis/dags/dbt/ipea/models/sources.yml +++ b/airflow_lappis/dags/dbt/ipea/models/sources.yml @@ -25,3 +25,9 @@ sources: - name: programacao_financeira - name: programas + - name: siorg + schema: siorg + tables: + - name: estrutura_organizacional_cargos + - name: cargos_funcao + - name: unidade_organizacional From 5530895395987743a44b1ced4dd3a60e6ce5683f Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Fri, 23 May 2025 14:36:29 -0300 Subject: [PATCH 110/317] feat(dbt): adiciona modelos dbt do siorg --- .../pessoas_dbt/bronze/cargos_funcoes.sql | 59 +++++++++++ .../bronze/unidade_organizacional.sql | 25 +++++ .../ipea/models/siape_dbt/bronze/dados_pa.sql | 70 ++++++------ .../siape_dbt/bronze/dados_pessoais.sql | 100 +++++++++--------- .../models/siape_dbt/bronze/dados_uorg.sql | 86 +++++++-------- .../siape_dbt/bronze/lista_servidores.sql | 18 ++-- .../models/siape_dbt/bronze/lista_uorgs.sql | 19 ++-- 7 files changed, 233 insertions(+), 144 deletions(-) create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/cargos_funcoes.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/unidade_organizacional.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/cargos_funcoes.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/cargos_funcoes.sql new file mode 100644 index 00000000..7505525c --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/cargos_funcoes.sql @@ -0,0 +1,59 @@ +with + fonte as ( + select + codigotipo, + nome, + sigla, + codigocargofuncao, + categoria, + nivel, + regraautoridade, + atonormativo__tipoato, + atonormativo__codigounidade, + atonormativo__numero, + atonormativo__dataassinatura, + atonormativo__datapublicacao, + atonormativo__datavigencia, + atonormativo__ementa, + atonormativo__url, + atonormativo__codigotipo, + atonormativo__siglatipo, + denominacoes__denominacao + from {{ source("siorg", "cargos_funcao") }} + ), + + denominacoes_expandidas as ( + select + f.*, + denominacao_elem ->> 'codigo' as denominacao_codigo, + denominacao_elem ->> 'descricao' as denominacao_descricao + from + fonte f, + lateral jsonb_array_elements( + replace( + replace(f.denominacoes__denominacao, '''', '"'), 'None', 'null' + )::jsonb + ) as denominacao_elem + ) + +select + codigotipo, + nome, + sigla, + codigocargofuncao, + categoria, + nivel, + regraautoridade, + atonormativo__tipoato, + atonormativo__codigounidade, + atonormativo__numero, + atonormativo__dataassinatura, + atonormativo__datapublicacao, + atonormativo__datavigencia, + atonormativo__ementa, + atonormativo__url, + atonormativo__codigotipo, + atonormativo__siglatipo, + denominacao_codigo, + denominacao_descricao +from denominacoes_expandidas diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/unidade_organizacional.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/unidade_organizacional.sql new file mode 100644 index 00000000..cebb963d --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/unidade_organizacional.sql @@ -0,0 +1,25 @@ +with + fonte as ( + select + regexp_replace(codigounidade, '^.*/', '') as codigounidade, + regexp_replace(codigounidadepai, '^.*/', '') as codigounidadepai, + regexp_replace(codigoorgaoentidade, '^.*/', '') as codigoorgaoentidade, + regexp_replace(codigotipounidade, '^.*/', '') as codigotipounidade, + nome, + sigla, + regexp_replace(codigoesfera, '^.*/', '') as codigoesfera, + regexp_replace(codigopoder, '^.*/', '') as codigopoder, + regexp_replace(codigonaturezajuridica, '^.*/', '') as codigonaturezajuridica, + codigosubnaturezajuridica, + nivelnormatizacao, + versaoconsulta, + datainicialversaoconsulta, + datafinalversaoconsulta, + operacao, + codigounidadepaianterior, + codigoorgaoentidadeanterior + from {{ source("siorg", "unidade_organizacional") }} + ) + +select * +from fonte diff --git a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_pa.sql b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_pa.sql index ea056ae8..c4915c80 100644 --- a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_pa.sql +++ b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_pa.sql @@ -1,34 +1,38 @@ +with + dados_pa as ( + select + agenciabeneficiario, + bancobeneficiario, + codorgao, + contabeneficiario, + cpfbeneficiario, + matricula, + nomebeneficiario, + valorultimapensao, + cpf, + codvinculoservidor, + nomealimentado, + nomevinculoservidor + from {{ source("siape", "dados_pa") }} + ) - -WITH dados_pa AS ( - SELECT - agenciabeneficiario, - bancobeneficiario, - codorgao, - contabeneficiario, - cpfbeneficiario, - matricula, - nomebeneficiario, - valorultimapensao, - cpf, - codvinculoservidor, - nomealimentado, - nomevinculoservidor - FROM {{ source('siape', 'dados_pa') }} -) - -SELECT - REGEXP_REPLACE(NULLIF(TRIM(agenciabeneficiario), ''), '[^0-9]', '', 'g') AS agencia_beneficiario, - NULLIF(TRIM(bancobeneficiario), '') AS banco_beneficiario, - NULLIF(TRIM(codorgao), '') AS cod_orgao, - UPPER(REGEXP_REPLACE(NULLIF(TRIM(contabeneficiario), ''), '[^0-9A-Za-z]', '', 'g')) AS conta_beneficiario, - REGEXP_REPLACE(NULLIF(TRIM(cpfbeneficiario), ''), '[^0-9]', '', 'g') AS cpf_beneficiario, - NULLIF(TRIM(matricula), '') AS matricula_servidor, - NULLIF(TRIM(nomebeneficiario), '') AS nome_beneficiario, - NULLIF(TRIM(valorultimapensao), '') AS valor_ultima_pensao, - REGEXP_REPLACE(NULLIF(TRIM(cpf), ''), '[^0-9]', '', 'g') AS cpf_servidor, - NULLIF(TRIM(codvinculoservidor), '') AS cod_vinculo_servidor, - NULLIF(TRIM(nomealimentado), '') AS nome_alimentado, - NULLIF(TRIM(nomevinculoservidor), '') AS nome_vinculo_servidor -FROM - dados_pa \ No newline at end of file +select + regexp_replace( + nullif(trim(agenciabeneficiario), ''), '[^0-9]', '', 'g' + ) as agencia_beneficiario, + nullif(trim(bancobeneficiario), '') as banco_beneficiario, + nullif(trim(codorgao), '') as cod_orgao, + upper( + regexp_replace(nullif(trim(contabeneficiario), ''), '[^0-9A-Za-z]', '', 'g') + ) as conta_beneficiario, + regexp_replace( + nullif(trim(cpfbeneficiario), ''), '[^0-9]', '', 'g' + ) as cpf_beneficiario, + nullif(trim(matricula), '') as matricula_servidor, + nullif(trim(nomebeneficiario), '') as nome_beneficiario, + nullif(trim(valorultimapensao), '') as valor_ultima_pensao, + regexp_replace(nullif(trim(cpf), ''), '[^0-9]', '', 'g') as cpf_servidor, + nullif(trim(codvinculoservidor), '') as cod_vinculo_servidor, + nullif(trim(nomealimentado), '') as nome_alimentado, + nullif(trim(nomevinculoservidor), '') as nome_vinculo_servidor +from dados_pa diff --git a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_pessoais.sql b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_pessoais.sql index 68599975..dfa338f0 100644 --- a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_pessoais.sql +++ b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_pessoais.sql @@ -1,52 +1,50 @@ +with + dados_pessoais as ( + select + codcor, + codestadocivil, + codnacionalidade, + codsexo, + datanascimento, + gruposanguineo, + nome, + nomecor, + nomeestadocivil, + nomemae, + nomemunicipnasc, + nomenacionalidade, + nomepai, + nomesexo, + numpispasep, + ufnascimento, + cpf, + coddeffisica, + nomedeffisica, + datachegbrasil, + nomepais + from {{ source("siape", "dados_pessoais") }} + ) - -WITH dados_pessoais AS ( - SELECT - codcor, - codestadocivil, - codnacionalidade, - codsexo, - datanascimento, - gruposanguineo, - nome, - nomecor, - nomeestadocivil, - nomemae, - nomemunicipnasc, - nomenacionalidade, - nomepai, - nomesexo, - numpispasep, - ufnascimento, - cpf, - coddeffisica, - nomedeffisica, - datachegbrasil, - nomepais - FROM {{ source('siape', 'dados_pessoais') }} -) - -SELECT - NULLIF(TRIM(codcor), '') AS cod_cor, - NULLIF(TRIM(codestadocivil), '') AS cod_estado_civil, - NULLIF(TRIM(codnacionalidade), '') AS cod_nacionalidade, - NULLIF(TRIM(codsexo), '') AS cod_sexo, - TO_DATE(NULLIF(TRIM(datanascimento), ''), 'DDMMYYYY') AS dt_nascimento, - NULLIF(TRIM(gruposanguineo), '') AS grupo_sanguineo, - NULLIF(TRIM(nome), '') AS nome_pessoa, - NULLIF(TRIM(nomecor), '') AS nome_cor, - NULLIF(TRIM(nomeestadocivil), '') AS nome_estado_civil, - NULLIF(TRIM(nomemae), '') AS nome_mae, - NULLIF(TRIM(nomemunicipnasc), '') AS nome_municipio_nascimento, - NULLIF(TRIM(nomenacionalidade), '') AS nome_nacionalidade, - NULLIF(NULLIF(TRIM(nomepai), ''), 'NAO DECLARADO') AS nome_pai, - NULLIF(TRIM(nomesexo), '') AS nome_sexo, - REGEXP_REPLACE(NULLIF(TRIM(numpispasep), ''), '[^0-9]', '', 'g') AS num_pispasep, - UPPER(NULLIF(TRIM(ufnascimento), '')) AS uf_nascimento, - REGEXP_REPLACE(NULLIF(TRIM(cpf), ''), '[^0-9]', '', 'g') AS cpf, - NULLIF(TRIM(coddeffisica), '') AS cod_deficiencia_fisica, - NULLIF(TRIM(nomedeffisica), '') AS nome_deficiencia_fisica, - TO_DATE(NULLIF(TRIM(datachegbrasil), ''), 'DDMMYYYY') AS dt_chegada_brasil, - NULLIF(TRIM(nomepais), '') AS nome_pais_origem -FROM - dados_pessoais \ No newline at end of file +select + nullif(trim(codcor), '') as cod_cor, + nullif(trim(codestadocivil), '') as cod_estado_civil, + nullif(trim(codnacionalidade), '') as cod_nacionalidade, + nullif(trim(codsexo), '') as cod_sexo, + to_date(nullif(trim(datanascimento), ''), 'DDMMYYYY') as dt_nascimento, + nullif(trim(gruposanguineo), '') as grupo_sanguineo, + nullif(trim(nome), '') as nome_pessoa, + nullif(trim(nomecor), '') as nome_cor, + nullif(trim(nomeestadocivil), '') as nome_estado_civil, + nullif(trim(nomemae), '') as nome_mae, + nullif(trim(nomemunicipnasc), '') as nome_municipio_nascimento, + nullif(trim(nomenacionalidade), '') as nome_nacionalidade, + nullif(nullif(trim(nomepai), ''), 'NAO DECLARADO') as nome_pai, + nullif(trim(nomesexo), '') as nome_sexo, + regexp_replace(nullif(trim(numpispasep), ''), '[^0-9]', '', 'g') as num_pispasep, + upper(nullif(trim(ufnascimento), '')) as uf_nascimento, + regexp_replace(nullif(trim(cpf), ''), '[^0-9]', '', 'g') as cpf, + nullif(trim(coddeffisica), '') as cod_deficiencia_fisica, + nullif(trim(nomedeffisica), '') as nome_deficiencia_fisica, + to_date(nullif(trim(datachegbrasil), ''), 'DDMMYYYY') as dt_chegada_brasil, + nullif(trim(nomepais), '') as nome_pais_origem +from dados_pessoais diff --git a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_uorg.sql b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_uorg.sql index 6deadbfa..86a6fa32 100644 --- a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_uorg.sql +++ b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_uorg.sql @@ -1,44 +1,44 @@ -WITH dados_uorg AS ( - SELECT - bairrouorg, - cepuorg, - codmatricula, - codmunicipiouorg, - codorgao, - codorgaouorg, - emailuorg, - enduorg, - logradourouorg, - nomemunicipiouorg, - nomeuorg, - numtelefoneuorg, - numerouorg, - siglauorg, - ufuorg, - cpf, - complementouorg, - numfaxuorg - FROM {{ source('siape', 'dados_uorg') }} -) +with + dados_uorg as ( + select + bairrouorg, + cepuorg, + codmatricula, + codmunicipiouorg, + codorgao, + codorgaouorg, + emailuorg, + enduorg, + logradourouorg, + nomemunicipiouorg, + nomeuorg, + numtelefoneuorg, + numerouorg, + siglauorg, + ufuorg, + cpf, + complementouorg, + numfaxuorg + from {{ source("siape", "dados_uorg") }} + ) -SELECT - NULLIF(TRIM(bairrouorg), '') AS bairro_uorg, - REGEXP_REPLACE(NULLIF(TRIM(cepuorg), ''), '[^0-9]', '', 'g') AS cep_uorg, - NULLIF(TRIM(codmatricula), '') AS codigo_matricula, - NULLIF(TRIM(codmunicipiouorg), '') AS codigo_municipio_uorg, - NULLIF(TRIM(codorgao), '') AS codigo_orgao, - NULLIF(TRIM(codorgaouorg), '') AS codigo_orgao_u - LOWER(NULLIF(TRIM(emailuorg), '')) AS email_uorg, - NULLIF(TRIM(enduorg), '') AS tipo_endereco_uorg, - NULLIF(TRIM(logradourouorg), '') AS logradouro_uorg, - NULLIF(TRIM(nomemunicipiouorg), '') AS nome_municipio_uorg, - NULLIF(TRIM(nomeuorg), '') AS nome_uorg, - REGEXP_REPLACE(NULLIF(TRIM(numtelefoneuorg), ''), '[^0-9]', '', 'g') AS telefone_uorg, - NULLIF(TRIM(numerouorg), '') AS numero_endereco_uorg, - NULLIF(TRIM(siglauorg), '') AS sigla_uorg, - UPPER(NULLIF(TRIM(ufuorg), '')) AS uf_uorg, - REGEXP_REPLACE(NULLIF(TRIM(cpf), ''), '[^0-9]', '', 'g') AS cpf, - NULLIF(NULLIF(TRIM(complementouorg), ''), '---') AS complemento_endereco_uorg, - REGEXP_REPLACE(NULLIF(TRIM(numfaxuorg), ''), '[^0-9]', '', 'g') AS fax_uorg -FROM - dados_uorg +select + nullif(trim(bairrouorg), '') as bairro_uorg, + regexp_replace(nullif(trim(cepuorg), ''), '[^0-9]', '', 'g') as cep_uorg, + nullif(trim(codmatricula), '') as codigo_matricula, + nullif(trim(codmunicipiouorg), '') as codigo_municipio_uorg, + nullif(trim(codorgao), '') as codigo_orgao, + nullif(trim(codorgaouorg), '') as codigo_orgao_u + lower(nullif(trim(emailuorg), '')) as email_uorg, + nullif(trim(enduorg), '') as tipo_endereco_uorg, + nullif(trim(logradourouorg), '') as logradouro_uorg, + nullif(trim(nomemunicipiouorg), '') as nome_municipio_uorg, + nullif(trim(nomeuorg), '') as nome_uorg, + regexp_replace(nullif(trim(numtelefoneuorg), ''), '[^0-9]', '', 'g') as telefone_uorg, + nullif(trim(numerouorg), '') as numero_endereco_uorg, + nullif(trim(siglauorg), '') as sigla_uorg, + upper(nullif(trim(ufuorg), '')) as uf_uorg, + regexp_replace(nullif(trim(cpf), ''), '[^0-9]', '', 'g') as cpf, + nullif(nullif(trim(complementouorg), ''), '---') as complemento_endereco_uorg, + regexp_replace(nullif(trim(numfaxuorg), ''), '[^0-9]', '', 'g') as fax_uorg +from dados_uorg diff --git a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/lista_servidores.sql b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/lista_servidores.sql index 0ef1975e..bcc0c7f4 100644 --- a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/lista_servidores.sql +++ b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/lista_servidores.sql @@ -1,9 +1,11 @@ -WITH lista_servidores AS ( - SELECT - cpf, - TO_DATE(dataultimatransacao, 'DDMMYYYY') AS dt_ultima_transacao, - coduorg AS cod_uorg - FROM {{ source('siape', 'lista_servidores') }} -) +with + lista_servidores as ( + select + cpf, + to_date(dataultimatransacao, 'DDMMYYYY') as dt_ultima_transacao, + coduorg as cod_uorg + from {{ source("siape", "lista_servidores") }} + ) -SELECT * FROM lista_servidores +select * +from lista_servidores diff --git a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/lista_uorgs.sql b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/lista_uorgs.sql index 6758e481..6e2d832e 100644 --- a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/lista_uorgs.sql +++ b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/lista_uorgs.sql @@ -1,10 +1,11 @@ +with + lista_uorgs as ( + select + cast(codigo as int) as codigo, + to_date(dataultimatransacao, 'DDMMYYYY') as dt_ultima_transacao, + nome + from {{ source("siape", "lista_uorgs") }} + ) -WITH lista_uorgs AS ( - SELECT - cast(codigo AS INT) AS codigo, - TO_DATE(dataultimatransacao, 'DDMMYYYY') AS dt_ultima_transacao, - nome - FROM {{ source('siape', 'lista_uorgs') }} -) - -SELECT * FROM lista_uorgs +select * +from lista_uorgs From e7beaf4647dc332b0053922a4e66786f531e60fa Mon Sep 17 00:00:00 2001 From: Joyce Dionizio Date: Wed, 28 May 2025 15:46:32 +0000 Subject: [PATCH 111/317] fix: evita que o sonar acesse arquivos com permissao negada --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 58a8a7b1..2f14c954 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,7 +6,7 @@ variables: POETRY_HOME: "/opt/poetry" POETRY_VERSION: "1.8.5" POETRY_VIRTUALENVS_IN_PROJECT: "true" - POETRY_CACHE_DIR: "$CI_PROJECT_DIR/.cache/poetry" + POETRY_CACHE_DIR: "/tmp/poetry-cache" PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" DBT_PROJECT_DIR: "${CI_PROJECT_DIR}/plugins/dbt" GIT_CI_USER: "ci bot" @@ -16,7 +16,7 @@ variables: cache: paths: - - .cache/poetry + # - .cache/poetry - .cache/pip - .venv - .cache/docker @@ -61,7 +61,7 @@ scan: stage: test image: sonarsource/sonar-scanner-cli:latest script: - - sonar-scanner -Dsonar.projectKey=$SONAR_PROJECT_KEY -Dsonar.organization=$SONAR_ORG -Dsonar.host.url=https://sonarcloud.io -Dsonar.login=$SONAR_TOKEN + - sonar-scanner -Dsonar.projectKey=$SONAR_PROJECT_KEY -Dsonar.organization=$SONAR_ORG -Dsonar.host.url=https://sonarcloud.io -Dsonar.login=$SONAR_TOKEN -Dsonar.exclusions=**/.cache/poetry/**/*,**/.venv/**/*,**/.cache/pip/**/* rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - changes: From ca881fb08dd13b57a82116ed1d6bce7c68c318a2 Mon Sep 17 00:00:00 2001 From: Joyce Date: Wed, 28 May 2025 08:16:16 -0300 Subject: [PATCH 112/317] feat: adiciona logging pra debugar --- .../data_ingest/dados_pessoais_siape_ingest_dag.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/dados_pessoais_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_pessoais_siape_ingest_dag.py index 26c42bc7..e53f56c4 100644 --- a/airflow_lappis/dags/data_ingest/dados_pessoais_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_pessoais_siape_ingest_dag.py @@ -37,6 +37,8 @@ def fetch_and_store_dados_pessoais() -> None: for cpf in cpfs: try: + logging.info(f"Processando CPF: {cpf}") + context = { "siglaSistema": "PETRVS-IPEA", "nomeSistema": "PDG-PETRVS-IPEA", @@ -48,13 +50,17 @@ def fetch_and_store_dados_pessoais() -> None: } resposta_xml = cliente_siape.call("consultaDadosPessoais.xml.j2", context) + logging.debug(f"XML bruto para CPF {cpf}:\n{resposta_xml}") + dados = ClienteSiape.parse_xml_to_dict(resposta_xml) + logging.debug(f"Dados parseados para CPF {cpf}: {dados}") if not dados: logging.warning(f"Nenhum dado pessoal encontrado para CPF {cpf}") continue dados["cpf"] = cpf + logging.info(f"Dados finais prontos para inserção: {dados}") db.alter_table( data=dados, @@ -70,10 +76,10 @@ def fetch_and_store_dados_pessoais() -> None: schema="siape", ) - logging.info(f"Dado pessoal inserido para CPF {cpf}") + logging.info(f"Dado pessoal inserido com sucesso para CPF {cpf}") except Exception as e: - logging.error(f"Erro ao processar CPF {cpf}: {e}") + logging.error(f"Erro ao processar CPF {cpf}: {e}", exc_info=True) continue fetch_and_store_dados_pessoais() From 693b670bbd574ae8b4b490fe50f88d90bf99cf91 Mon Sep 17 00:00:00 2001 From: Joyce Dionizio Date: Wed, 28 May 2025 16:37:48 +0000 Subject: [PATCH 113/317] feat: adiciona dag de debug --- .../dags/data_ingest/dag_debug_siape_dag.py | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/dag_debug_siape_dag.py diff --git a/airflow_lappis/dags/data_ingest/dag_debug_siape_dag.py b/airflow_lappis/dags/data_ingest/dag_debug_siape_dag.py new file mode 100644 index 00000000..df414beb --- /dev/null +++ b/airflow_lappis/dags/data_ingest/dag_debug_siape_dag.py @@ -0,0 +1,61 @@ +import os +from datetime import datetime, timedelta +from airflow.decorators import dag, task +from postgres_helpers import get_postgres_conn +from cliente_siape import ClienteSiape +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@once", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Joyce", + "retries": 0, + "retry_delay": timedelta(minutes=5), + }, + tags=["debug", "siape", "dados_pessoais"], +) +def siape_dados_pessoais_debug() -> None: + + @task + def testar_insercao_siape() -> None: + cliente_siape = ClienteSiape() + db = ClientPostgresDB(get_postgres_conn()) + + cpf = "13086455705" + context = { + "siglaSistema": "PETRVS-IPEA", + "nomeSistema": "PDG-PETRVS-IPEA", + "senha": os.getenv("SIAPE_PASSWORD_USER"), + "cpf": cpf, + "codOrgao": "45206", + "parmExistPag": "b", + "parmTipoVinculo": "c", + } + + xml = cliente_siape.call("consultaDadosPessoais.xml.j2", context) + dados = ClienteSiape.parse_xml_to_dict(xml) + + if not dados: + raise ValueError(f"Nenhum dado retornado para CPF {cpf}") + + dados["cpf"] = cpf + + print(f"🔍 Dados a serem inseridos: {dados}") + + # db.alter_table(data=dados, table_name="dados_pessoais", schema="siape") + + db.insert_data( + [dados], + table_name="dados_pessoais", + conflict_fields=["cpf"], + primary_key=["cpf"], + schema="siape", + ) + + testar_insercao_siape() + + +dag_instance = siape_dados_pessoais_debug() From 6ff919ac9f9358f27083ccd81c5bcfe5d7fe9095 Mon Sep 17 00:00:00 2001 From: Joyce Dionizio Date: Thu, 29 May 2025 18:24:53 +0000 Subject: [PATCH 114/317] fix(dag): adiciona pk na tabela --- Makefile | 2 +- .../dados_pessoais_siape_ingest_dag.py | 43 +++++++++++++ .../dags/data_ingest/dag_debug_siape_dag.py | 61 ------------------- 3 files changed, 44 insertions(+), 62 deletions(-) delete mode 100644 airflow_lappis/dags/data_ingest/dag_debug_siape_dag.py diff --git a/Makefile b/Makefile index 58b5b6a6..96541abd 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ format: lint: poetry run black . --check poetry run ruff check . - poetry run mypy . --explicit-package-bases --install-types + poetry run mypy . --explicit-package-bases --install-types --non-interactive poetry run sqlfmt ./airflow_lappis/dags/dbt --check [ "${GITLAB_CI}" ] || poetry run sqlfluff lint ./airflow_lappis/dags/dbt diff --git a/airflow_lappis/dags/data_ingest/dados_pessoais_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_pessoais_siape_ingest_dag.py index e53f56c4..09684053 100644 --- a/airflow_lappis/dags/data_ingest/dados_pessoais_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_pessoais_siape_ingest_dag.py @@ -31,6 +31,49 @@ def fetch_and_store_dados_pessoais() -> None: postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) + # --- Garantia de schema, tabela e PRIMARY KEY --- + logging.info("Verificando existência da tabela e constraint PRIMARY KEY") + ddl = """ + DO $$ + BEGIN + -- Cria schema se não existir + IF NOT EXISTS ( + SELECT 1 FROM information_schema.schemata WHERE schema_name = 'siape' + ) THEN + EXECUTE 'CREATE SCHEMA siape'; + END IF; + + -- Cria tabela se não existir + IF NOT EXISTS ( + SELECT 1 FROM information_schema.tables + WHERE table_schema = 'siape' AND table_name = 'dados_pessoais' + ) THEN + EXECUTE ' + CREATE TABLE siape.dados_pessoais ( + cpf TEXT PRIMARY KEY -- Define cpf como PK já na criação + ) + '; + END IF; + + -- Adiciona PK se a tabela existe mas ainda não tem + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'siape' + AND table_name = 'dados_pessoais' + AND constraint_type = 'PRIMARY KEY' + ) THEN + EXECUTE ( + 'ALTER TABLE siape.dados_pessoais ADD CONSTRAINT dados_pessoais_pkey ' + 'PRIMARY KEY (cpf)' + ); + END IF; + END + $$; + """ + db.execute_query(ddl) + logging.info("Estrutura da tabela verificada/criada com sucesso.") + + # --- Continua fluxo normal --- query = "SELECT DISTINCT cpf FROM siape.lista_servidores WHERE cpf IS NOT NULL" cpfs = [row[0] for row in db.execute_query(query)] logging.info(f"Total de CPFs encontrados: {len(cpfs)}") diff --git a/airflow_lappis/dags/data_ingest/dag_debug_siape_dag.py b/airflow_lappis/dags/data_ingest/dag_debug_siape_dag.py deleted file mode 100644 index df414beb..00000000 --- a/airflow_lappis/dags/data_ingest/dag_debug_siape_dag.py +++ /dev/null @@ -1,61 +0,0 @@ -import os -from datetime import datetime, timedelta -from airflow.decorators import dag, task -from postgres_helpers import get_postgres_conn -from cliente_siape import ClienteSiape -from cliente_postgres import ClientPostgresDB - - -@dag( - schedule_interval="@once", - start_date=datetime(2023, 1, 1), - catchup=False, - default_args={ - "owner": "Joyce", - "retries": 0, - "retry_delay": timedelta(minutes=5), - }, - tags=["debug", "siape", "dados_pessoais"], -) -def siape_dados_pessoais_debug() -> None: - - @task - def testar_insercao_siape() -> None: - cliente_siape = ClienteSiape() - db = ClientPostgresDB(get_postgres_conn()) - - cpf = "13086455705" - context = { - "siglaSistema": "PETRVS-IPEA", - "nomeSistema": "PDG-PETRVS-IPEA", - "senha": os.getenv("SIAPE_PASSWORD_USER"), - "cpf": cpf, - "codOrgao": "45206", - "parmExistPag": "b", - "parmTipoVinculo": "c", - } - - xml = cliente_siape.call("consultaDadosPessoais.xml.j2", context) - dados = ClienteSiape.parse_xml_to_dict(xml) - - if not dados: - raise ValueError(f"Nenhum dado retornado para CPF {cpf}") - - dados["cpf"] = cpf - - print(f"🔍 Dados a serem inseridos: {dados}") - - # db.alter_table(data=dados, table_name="dados_pessoais", schema="siape") - - db.insert_data( - [dados], - table_name="dados_pessoais", - conflict_fields=["cpf"], - primary_key=["cpf"], - schema="siape", - ) - - testar_insercao_siape() - - -dag_instance = siape_dados_pessoais_debug() From bed849eb96bb2ccf67782fb8e352759cce3ca03d Mon Sep 17 00:00:00 2001 From: Joyce Date: Thu, 29 May 2025 15:35:21 -0300 Subject: [PATCH 115/317] feat(plugin): separa responsabilidades das queries --- .../dados_pessoais_siape_ingest_dag.py | 2 +- airflow_lappis/plugins/cliente_postgres.py | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/airflow_lappis/dags/data_ingest/dados_pessoais_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_pessoais_siape_ingest_dag.py index 09684053..bf30d99c 100644 --- a/airflow_lappis/dags/data_ingest/dados_pessoais_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_pessoais_siape_ingest_dag.py @@ -70,7 +70,7 @@ def fetch_and_store_dados_pessoais() -> None: END $$; """ - db.execute_query(ddl) + db.execute_non_query(ddl) logging.info("Estrutura da tabela verificada/criada com sucesso.") # --- Continua fluxo normal --- diff --git a/airflow_lappis/plugins/cliente_postgres.py b/airflow_lappis/plugins/cliente_postgres.py index 7687938a..293e4824 100755 --- a/airflow_lappis/plugins/cliente_postgres.py +++ b/airflow_lappis/plugins/cliente_postgres.py @@ -390,3 +390,23 @@ def get_codigo_unidade(self) -> list[int]: cursor.execute(query) codigo_unidade = [int(row[0]) for row in cursor.fetchall()] return codigo_unidade + + def execute_non_query(self, query: str) -> None: + """ + Executa uma query que não retorna resultados (como DDL ou blocos DO $$). + + Args: + query (str): Comando SQL que não retorna resultados. + """ + logging.info(f"[cliente_postgres.py] Executando non-query: {query}") + with psycopg2.connect(self.conn_str) as conn: + with conn.cursor() as cursor: + try: + cursor.execute(query) + conn.commit() + logging.info("[cliente_postgres.py] Non-query executado com sucesso") + except psycopg2.Error as e: + logging.error( + f"[cliente_postgres.py] Erro ao executar non-query. Erro: {e}" + ) + raise RuntimeError("Erro ao executar comando SQL sem retorno") from e From 2ba491d3b9b24eb6fae9876afff0447134a242b3 Mon Sep 17 00:00:00 2001 From: Joyce Date: Thu, 29 May 2025 16:37:47 -0300 Subject: [PATCH 116/317] fix(dag): corrige problema na dag --- .../data_ingest/dados_pa_siape_ingest_dag.py | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/airflow_lappis/dags/data_ingest/dados_pa_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_pa_siape_ingest_dag.py index 443eefcd..064dd566 100644 --- a/airflow_lappis/dags/data_ingest/dados_pa_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_pa_siape_ingest_dag.py @@ -31,12 +31,49 @@ def fetch_and_store_dados_pa() -> None: postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) + # Garante que schema, tabela e chave primária existam + ddl = """ + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.schemata WHERE schema_name = 'siape' + ) THEN + EXECUTE 'CREATE SCHEMA siape'; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM information_schema.tables + WHERE table_schema = 'siape' AND table_name = 'dados_pa' + ) THEN + EXECUTE ' + CREATE TABLE siape.dados_pa ( + cpf TEXT PRIMARY KEY + ) + '; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'siape' + AND table_name = 'dados_pa' + AND constraint_type = 'PRIMARY KEY' + ) THEN + EXECUTE 'ALTER TABLE siape.dados_pa ADD CONSTRAINT dados_pa_pkey ' || + 'PRIMARY KEY (cpf)'; + END IF; + END + $$; + """ + db.execute_non_query(ddl) # Assumindo que esse método executa sem fetch + logging.info("Estrutura da tabela verificada/criada com sucesso.") + query = "SELECT DISTINCT cpf FROM siape.lista_servidores WHERE cpf IS NOT NULL" cpfs = [row[0] for row in db.execute_query(query)] logging.info(f"Total de CPFs encontrados: {len(cpfs)}") for cpf in cpfs: try: + logging.info(f"Processando CPF: {cpf}") context = { "siglaSistema": "PETRVS-IPEA", "nomeSistema": "PDG-PETRVS-IPEA", @@ -73,7 +110,7 @@ def fetch_and_store_dados_pa() -> None: logging.info(f"Plano de atuação inserido para CPF {cpf}") except Exception as e: - logging.error(f"Erro ao processar CPF {cpf}: {e}") + logging.error(f"Erro ao processar CPF {cpf}: {e}", exc_info=True) continue fetch_and_store_dados_pa() From de5477add89e33c889b655e37ebbd546f8d12e07 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Fri, 30 May 2025 12:25:44 -0300 Subject: [PATCH 117/317] feat(dag): adiciona dag de ingestao dos terceirizados --- .../dags/data_ingest/empenhos_ingest_dag.py | 2 +- .../data_ingest/terceirizados_ingest_dag.py | 56 +++++++++++++++++++ airflow_lappis/plugins/cliente_contratos.py | 30 ++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 airflow_lappis/dags/data_ingest/terceirizados_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/empenhos_ingest_dag.py b/airflow_lappis/dags/data_ingest/empenhos_ingest_dag.py index 63495ec9..0fd1f0af 100755 --- a/airflow_lappis/dags/data_ingest/empenhos_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/empenhos_ingest_dag.py @@ -18,7 +18,7 @@ tags=["empenhos_api", "compras_gov"], ) def api_empenhos_dag() -> None: - """DAG para buscar e armazenar empenhos de uma API no PostgreSQL.""" + """DAG para buscar e armazenar dados de empenhos de uma API.""" @task def fetch_empenhos() -> None: diff --git a/airflow_lappis/dags/data_ingest/terceirizados_ingest_dag.py b/airflow_lappis/dags/data_ingest/terceirizados_ingest_dag.py new file mode 100644 index 00000000..0475fb28 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/terceirizados_ingest_dag.py @@ -0,0 +1,56 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from postgres_helpers import get_postgres_conn +from cliente_contratos import ClienteContratos +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Davi", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["terceirizados_api", "compras_gov"], +) +def api_terceirizados_dag() -> None: + """DAG para buscar e armazenar dados de terceirizados de uma API.""" + + @task + def fetch_terceirizados() -> None: + logging.info("[terceirizados_ingest_dag.py] Starting fetch_terceirizados task") + api = ClienteContratos() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + contratos_ids = db.get_contratos_ids() + + for contrato_id in contratos_ids: + try: + logging.info(f"Fetching terceirizados for contrato ID: " f"{contrato_id}") + terceirizados = api.get_terceirizados_by_contrato_id(str(contrato_id)) + + logging.info( + f"Inserting terceirizados for contrato ID: " + f"{contrato_id} into PostgreSQL" + ) + db.insert_data( + terceirizados, + "terceirizados", + conflict_fields=["id"], + primary_key=["id"], + schema="compras_gov", + ) + except Exception as e: + logging.error( + f"[terceirizados_ingest_dag.py] Error fetching terceirizados for " + f"contrato ID {contrato_id}: {e}" + ) + + fetch_terceirizados() + + +dag_instance = api_terceirizados_dag() diff --git a/airflow_lappis/plugins/cliente_contratos.py b/airflow_lappis/plugins/cliente_contratos.py index ef3084d2..6711fcb0 100755 --- a/airflow_lappis/plugins/cliente_contratos.py +++ b/airflow_lappis/plugins/cliente_contratos.py @@ -159,3 +159,33 @@ def get_cronograma_by_contrato_id(self, contrato_id: str) -> list | None: f"{contrato_id} with status: {status}" ) return None + + def get_terceirizados_by_contrato_id(self, contrato_id: str) -> list | None: + """ + Obter todos os terceirizados de um contrato específico. + + Args: + contrato_id (str): id do contrato + + Returns: + list: os terceirizados de um contrato específico. + """ + endpoint = f"/contrato/{contrato_id}/terceirizados" + logging.info( + f"[cliente_contratos.py] Fetching terceirizados for contrato: {contrato_id}" + ) + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER + ) + if status == http.HTTPStatus.OK and isinstance(data, list): + logging.info( + "[cliente_contratos.py] Successfully fetched terceirizados for contrato: " + f"{contrato_id}" + ) + return data + else: + logging.warning( + "[cliente_contratos.py] Failed to fetch terceirizados for contrato ID: " + f"{contrato_id} with status: {status}" + ) + return None From 669306bff8f995a3f1c58c35ace9a0fbde766428 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Fri, 30 May 2025 16:52:11 -0300 Subject: [PATCH 118/317] feat(dag): adiciona task de ingestao de teds enviadas --- .../dags/data_ingest/programas_ingest_dag.py | 26 +++++++++++++++++++ airflow_lappis/plugins/cliente_ted.py | 19 ++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/airflow_lappis/dags/data_ingest/programas_ingest_dag.py b/airflow_lappis/dags/data_ingest/programas_ingest_dag.py index da12cebd..3783cce2 100644 --- a/airflow_lappis/dags/data_ingest/programas_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/programas_ingest_dag.py @@ -1,5 +1,6 @@ import logging from airflow.decorators import dag, task +from airflow.models import Variable from datetime import datetime, timedelta from postgres_helpers import get_postgres_conn from cliente_ted import ClienteTed @@ -53,7 +54,32 @@ def fetch_and_update_programas() -> None: logging.info(f"Completed processing {total_processed} programs") + @task + def fetch_and_update_programas_by_sigla() -> None: + logging.info("Starting fetch_and_update_programas_by_sigla - IPEA") + api = ClienteTed() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + sigla = Variable.get("airflow_orgao", default_var="IPEA").upper() + programas_data = api.get_programas_by_sigla_unidade_descentralizadora(sigla) + if programas_data and len(programas_data) > 0: + for programa in programas_data: + db.alter_table(programa, "programas", schema="transfere_gov") + db.insert_data( + programas_data, + "programas", + primary_key=["id_programa"], + conflict_fields=["id_programa"], + schema="transfere_gov", + ) + logging.info(f"Inserted/updated {len(programas_data)} programas for IPEA") + else: + logging.warning( + "No programas data found for sigla_unidade_descentralizadora=IPEA" + ) + fetch_and_update_programas() + fetch_and_update_programas_by_sigla() dag_instance = api_programas_dag() diff --git a/airflow_lappis/plugins/cliente_ted.py b/airflow_lappis/plugins/cliente_ted.py index fcc5e28b..49190515 100644 --- a/airflow_lappis/plugins/cliente_ted.py +++ b/airflow_lappis/plugins/cliente_ted.py @@ -133,3 +133,22 @@ def get_planos_acao_by_id_programa(self, id_programa: str) -> list | None: f"{id_programa} with status: {status}" ) return None + + def get_programas_by_sigla_unidade_descentralizadora(self, sigla: str) -> list | None: + endpoint = f"programa?sigla_unidade_descentralizadora=eq.{sigla}" + logging.info(f"Fetching programas for sigla_unidade_descentralizadora: {sigla}") + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER + ) + if status == http.HTTPStatus.OK and isinstance(data, list): + logging.info( + f"Successfully fetched programas for sigla_unidade_descentralizadora: " + f"{sigla}" + ) + return data + else: + logging.warning( + f"Failed to fetch programas for sigla_unidade_descentralizadora: " + f"{sigla} with status: {status}" + ) + return None From 894edf2d4c493fd3bc777f85a7167bdcc1ecabc0 Mon Sep 17 00:00:00 2001 From: Joyce Dionizio Date: Tue, 3 Jun 2025 17:42:46 +0000 Subject: [PATCH 119/317] Fix/dag ingestion siape --- .../dags/data_ingest/dados_pa_siape_ingest_dag.py | 6 +++--- .../dags/data_ingest/lista_uorgs_siape_ingest_dag.py | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/dados_pa_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_pa_siape_ingest_dag.py index 064dd566..d65f2752 100644 --- a/airflow_lappis/dags/data_ingest/dados_pa_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_pa_siape_ingest_dag.py @@ -91,7 +91,7 @@ def fetch_and_store_dados_pa() -> None: logging.warning(f"Nenhum dado PA encontrado para CPF {cpf}") continue - dados["cpf"] = cpf + dados["cpf_servidor"] = cpf db.alter_table( data=dados, @@ -102,8 +102,8 @@ def fetch_and_store_dados_pa() -> None: db.insert_data( [dados], table_name="dados_pa", - conflict_fields=["cpf"], - primary_key=["cpf"], + conflict_fields=["cpf_servidor"], + primary_key=["cpf_servidor"], schema="siape", ) diff --git a/airflow_lappis/dags/data_ingest/lista_uorgs_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/lista_uorgs_siape_ingest_dag.py index 44706ec5..af4c794d 100644 --- a/airflow_lappis/dags/data_ingest/lista_uorgs_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/lista_uorgs_siape_ingest_dag.py @@ -55,6 +55,10 @@ def fetch_and_store_lista_uorgs() -> None: logging.warning("Nenhum dado retornado da API listaUorgs") return + for item in dados_lista: + if "dataultimatransacao" in item: + item["dt_ultima_transacao"] = item.pop("dataultimatransacao") + postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) From 496ac7f80b0a1d34e79d7a3d89747a9bc39967b9 Mon Sep 17 00:00:00 2001 From: Joyce Dionizio Date: Tue, 3 Jun 2025 18:16:38 +0000 Subject: [PATCH 120/317] Fix/dag ingestion siape --- airflow_lappis/dags/data_ingest/dados_pa_siape_ingest_dag.py | 4 ++-- .../dags/data_ingest/lista_uorgs_siape_ingest_dag.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/dados_pa_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_pa_siape_ingest_dag.py index d65f2752..f78f7100 100644 --- a/airflow_lappis/dags/data_ingest/dados_pa_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_pa_siape_ingest_dag.py @@ -47,7 +47,7 @@ def fetch_and_store_dados_pa() -> None: ) THEN EXECUTE ' CREATE TABLE siape.dados_pa ( - cpf TEXT PRIMARY KEY + cpf_servidor TEXT PRIMARY KEY ) '; END IF; @@ -59,7 +59,7 @@ def fetch_and_store_dados_pa() -> None: AND constraint_type = 'PRIMARY KEY' ) THEN EXECUTE 'ALTER TABLE siape.dados_pa ADD CONSTRAINT dados_pa_pkey ' || - 'PRIMARY KEY (cpf)'; + 'PRIMARY KEY (cpf_servidor)'; END IF; END $$; diff --git a/airflow_lappis/dags/data_ingest/lista_uorgs_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/lista_uorgs_siape_ingest_dag.py index af4c794d..254ec583 100644 --- a/airflow_lappis/dags/data_ingest/lista_uorgs_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/lista_uorgs_siape_ingest_dag.py @@ -56,8 +56,8 @@ def fetch_and_store_lista_uorgs() -> None: return for item in dados_lista: - if "dataultimatransacao" in item: - item["dt_ultima_transacao"] = item.pop("dataultimatransacao") + if "dataUltimaTransacao" in item: + item["dt_ultima_transacao"] = item.pop("dataUltimaTransacao") postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) From a1730a0273b71dd0d6b0960a622e9f64335d3c8c Mon Sep 17 00:00:00 2001 From: Joyce Dionizio Date: Tue, 3 Jun 2025 19:35:52 +0000 Subject: [PATCH 121/317] fix: corrige data --- .../data_ingest/lista_uorgs_siape_ingest_dag.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/lista_uorgs_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/lista_uorgs_siape_ingest_dag.py index 254ec583..812da640 100644 --- a/airflow_lappis/dags/data_ingest/lista_uorgs_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/lista_uorgs_siape_ingest_dag.py @@ -1,7 +1,8 @@ import os import logging +from datetime import datetime from airflow.decorators import dag, task -from datetime import datetime, timedelta +from datetime import timedelta from postgres_helpers import get_postgres_conn from cliente_siape import ClienteSiape from cliente_postgres import ClientPostgresDB @@ -40,7 +41,6 @@ def fetch_and_store_lista_uorgs() -> None: resposta_xml = cliente_siape.call("listaUorgs.xml.j2", context) - # Define os namespaces e o elemento que queremos extrair ns = { "soapenv": "http://schemas.xmlsoap.org/soap/envelope/", "ns1": "http://servico.wssiapenet", @@ -57,7 +57,17 @@ def fetch_and_store_lista_uorgs() -> None: for item in dados_lista: if "dataUltimaTransacao" in item: - item["dt_ultima_transacao"] = item.pop("dataUltimaTransacao") + valor_bruto = item.pop("dataUltimaTransacao") + try: + if valor_bruto and valor_bruto.isdigit() and len(valor_bruto) == 8: + item["dt_ultima_transacao"] = ( + datetime.strptime(valor_bruto, "%d%m%Y").date().isoformat() + ) + else: + item["dt_ultima_transacao"] = None + except Exception as e: + logging.warning(f"Erro ao converter data: {valor_bruto} - {e}") + item["dt_ultima_transacao"] = None postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) From e6ad39284ad2da6d87df7f936abe009c3de0e6e6 Mon Sep 17 00:00:00 2001 From: Joyce Dionizio Date: Tue, 3 Jun 2025 20:03:05 +0000 Subject: [PATCH 122/317] Fix/dag lista uorgs --- .../lista_uorgs_siape_ingest_dag.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/airflow_lappis/dags/data_ingest/lista_uorgs_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/lista_uorgs_siape_ingest_dag.py index 812da640..b3bd7a49 100644 --- a/airflow_lappis/dags/data_ingest/lista_uorgs_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/lista_uorgs_siape_ingest_dag.py @@ -74,6 +74,24 @@ def fetch_and_store_lista_uorgs() -> None: logging.info("Inserindo dados no banco de dados") + ddl = """ + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'siape' + AND table_name = 'lista_uorgs' + AND constraint_type = 'PRIMARY KEY' + ) THEN + ALTER TABLE siape.lista_uorgs + ADD CONSTRAINT lista_uorgs_pkey + PRIMARY KEY (codigo); + END IF; + END + $$; + """ + db.execute_non_query(ddl) + db.insert_data( dados_lista, table_name="lista_uorgs", From ed3b1dc44154f55dd0f278fbbab057c819634e68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Freitas?= Date: Wed, 4 Jun 2025 03:13:06 +0000 Subject: [PATCH 123/317] Modelagem siape --- airflow_lappis/dags/dbt/ipea/dbt_project.yml | 10 +- .../bronze/afastamento_historico.sql | 130 ++ .../pessoas_dbt/bronze/dados_afastamento.sql | 106 ++ .../pessoas_dbt/bronze/dados_curriculo.sql | 43 + .../pessoas_dbt/bronze/dados_dependentes.sql | 50 + .../pessoas_dbt/bronze/dados_escolares.sql | 26 + .../pessoas_dbt/bronze/dados_financeiros.sql | 123 ++ .../pessoas_dbt/bronze/dados_funcionais.sql | 86 ++ .../bronze/dados_pa.sql | 29 + .../bronze/dados_pessoais.sql | 0 .../models/pessoas_dbt/bronze/dados_uorg.sql | 97 ++ .../bronze/lista_servidores.sql | 10 + .../models/pessoas_dbt/bronze/lista_uorgs.sql | 21 + .../pessoas_dbt/gold/cargos_consolidado.sql | 29 + .../gold/resumo_quadro_pessoal.sql | 8 + .../silver/afastamento_consolidado.sql | 51 + .../silver/servidores_detalhados.sql | 151 ++ .../dags/dbt/ipea/models/schema.yml | 1223 +++++++++++++++++ .../models/siape_dbt/bronze/dados_uorg.sql | 44 - .../models/siape_dbt/bronze/lista_uorgs.sql | 11 - .../dags/dbt/ipea/models/sources.yml | 7 + 21 files changed, 2195 insertions(+), 60 deletions(-) create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/afastamento_historico.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_afastamento.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_curriculo.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_dependentes.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_escolares.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_financeiros.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_funcionais.sql rename airflow_lappis/dags/dbt/ipea/models/{siape_dbt => pessoas_dbt}/bronze/dados_pa.sql (64%) rename airflow_lappis/dags/dbt/ipea/models/{siape_dbt => pessoas_dbt}/bronze/dados_pessoais.sql (100%) create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_uorg.sql rename airflow_lappis/dags/dbt/ipea/models/{siape_dbt => pessoas_dbt}/bronze/lista_servidores.sql (51%) create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_uorgs.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/cargos_consolidado.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/resumo_quadro_pessoal.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/afastamento_consolidado.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/servidores_detalhados.sql delete mode 100644 airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_uorg.sql delete mode 100644 airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/lista_uorgs.sql diff --git a/airflow_lappis/dags/dbt/ipea/dbt_project.yml b/airflow_lappis/dags/dbt/ipea/dbt_project.yml index 9fe908fe..5cb9f39f 100755 --- a/airflow_lappis/dags/dbt/ipea/dbt_project.yml +++ b/airflow_lappis/dags/dbt/ipea/dbt_project.yml @@ -37,11 +37,11 @@ models: +schema: ted views: +materialized: view - siape_dbt: - +materialized: table - +schema: siape - views: - +materialized: view + # siape_dbt: + # +materialized: table + # +schema: siape + # views: + # +materialized: view on-run-start: - '{{create_udfs()}}' \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/afastamento_historico.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/afastamento_historico.sql new file mode 100644 index 00000000..d2691627 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/afastamento_historico.sql @@ -0,0 +1,130 @@ +with + afastamento_historico_raw as ( + select + adiantamentosalarioferias, + anoexercicio, + datafim, + datafimaquisicao, + dataini, + datainicioaquisicao, + datainicioferiasinterrompidas, + diasrestantes, + gratificacaonatalina, + numerodaparcela, + parcelacontinuacaointerrupcao, + parcelainterrompida, + qtdedias, + cpf, + coddiplomaafastamento, + codocorrencia, + datapublicacaoafastamento, + descdiplomaafastamento, + descocorrencia, + numerodiplomaafastamento + -- grmatricula não está presente + from {{ source("siape", "afastamento_historico") }} + ), + + afastamento_historico_cleaned as ( + -- tive que adicionar um nullif adicional pois nas colunas tinha escrito "NaN" + -- como string, diferentemente das outras tabelas + select + nullif( + nullif(nullif(trim(adiantamentosalarioferias), ''), 'NaN'), '[null]' + ) as adiantamentosalarioferias_clean, + nullif( + nullif(nullif(trim(anoexercicio), ''), 'NaN'), '[null]' + ) as anoexercicio_clean, + nullif(nullif(nullif(trim(datafim), ''), 'NaN'), '[null]') as datafim_clean, + nullif( + nullif(nullif(trim(datafimaquisicao), ''), 'NaN'), '[null]' + ) as datafimaquisicao_clean, + nullif(nullif(nullif(trim(dataini), ''), 'NaN'), '[null]') as dataini_clean, + nullif( + nullif(nullif(trim(datainicioaquisicao), ''), 'NaN'), '[null]' + ) as datainicioaquisicao_clean, + nullif( + nullif(nullif(trim(datainicioferiasinterrompidas), ''), 'NaN'), '[null]' + ) as datainicioferiasinterrompidas_clean, + nullif( + nullif(nullif(trim(diasrestantes), ''), 'NaN'), '[null]' + ) as diasrestantes_clean, + nullif( + nullif(nullif(trim(gratificacaonatalina), ''), 'NaN'), '[null]' + ) as gratificacaonatalina_clean, + nullif( + nullif(nullif(trim(numerodaparcela), ''), 'NaN'), '[null]' + ) as numerodaparcela_clean, + nullif( + nullif(nullif(trim(parcelacontinuacaointerrupcao), ''), 'NaN'), '[null]' + ) as parcelacontinuacaointerrupcao_clean, + nullif( + nullif(nullif(trim(parcelainterrompida), ''), 'NaN'), '[null]' + ) as parcelainterrompida_clean, + nullif(nullif(nullif(trim(qtdedias), ''), 'NaN'), '[null]') as qtdedias_clean, + nullif(nullif(nullif(trim(cpf), ''), 'NaN'), '[null]') as cpf_clean, + nullif( + nullif(nullif(trim(coddiplomaafastamento), ''), 'NaN'), '[null]' + ) as coddiplomaafastamento_clean, + nullif( + nullif(nullif(trim(codocorrencia), ''), 'NaN'), '[null]' + ) as codocorrencia_clean, + nullif( + nullif(nullif(trim(datapublicacaoafastamento), ''), 'NaN'), '[null]' + ) as datapublicacaoafastamento_clean, + nullif( + nullif(nullif(trim(descdiplomaafastamento), ''), 'NaN'), '[null]' + ) as descdiplomaafastamento_clean, + nullif( + nullif(nullif(trim(descocorrencia), ''), 'NaN'), '[null]' + ) as descocorrencia_clean, + nullif( + nullif(nullif(trim(numerodiplomaafastamento), ''), 'NaN'), '[null]' + ) as numerodiplomaafastamento_clean + from afastamento_historico_raw + ) + +select + null as gr_matricula, -- placeholder para matrícula, pois aqui na tabela histórica não tem ... + adiantamentosalarioferias_clean as adiantamento_salario_ferias, + anoexercicio_clean as ano_exercicio, + -- mesma logica dos dados_afastamento, garantindo os comprimentos corretos + case + when length(datafim_clean) = 8 then to_date(datafim_clean, 'DDMMYYYY') else null + end as dt_fim, + case + when length(datafimaquisicao_clean) = 8 + then to_date(datafimaquisicao_clean, 'DDMMYYYY') + else null + end as dt_fim_aquisicao, + case + when length(dataini_clean) = 8 then to_date(dataini_clean, 'DDMMYYYY') else null + end as dt_ini, + case + when length(datainicioaquisicao_clean) = 8 + then to_date(datainicioaquisicao_clean, 'DDMMYYYY') + else null + end as dt_inicio_aquisicao, + case + when length(datainicioferiasinterrompidas_clean) = 8 + then to_date(datainicioferiasinterrompidas_clean, 'DDMMYYYY') + else null + end as dt_inicio_ferias_interrompidas, + cast(diasrestantes_clean as int) as dias_restantes, + gratificacaonatalina_clean as gratificacao_natalina, + cast(numerodaparcela_clean as int) as numero_parcela, + parcelacontinuacaointerrupcao_clean as parcela_continuacao_interrupcao, + parcelainterrompida_clean as parcela_interrompida, + cast(qtdedias_clean as int) as qtde_dias, + regexp_replace(cpf_clean, '[^0-9]', '', 'g') as cpf, + coddiplomaafastamento_clean as cod_diploma_afastamento, + codocorrencia_clean as cod_ocorrencia, + case + when length(datapublicacaoafastamento_clean) = 8 + then to_date(datapublicacaoafastamento_clean, 'YYYYMMDD') -- Formato YYYYMMDD + else null + end as dt_publicacao_afastamento, + descdiplomaafastamento_clean as desc_diploma_afastamento, + descocorrencia_clean as desc_ocorrencia, + cast(numerodiplomaafastamento_clean as int) as numero_diploma_afastamento +from afastamento_historico_cleaned diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_afastamento.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_afastamento.sql new file mode 100644 index 00000000..d7cec9be --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_afastamento.sql @@ -0,0 +1,106 @@ +with + dados_afastamento_raw as ( + select + adiantamentosalarioferias, + anoexercicio, + datafim, + datafimaquisicao, + dataini, + datainicioaquisicao, + gratificacaonatalina, + numerodaparcela, + parcelacontinuacaointerrupcao, + parcelainterrompida, + qtdedias, + grmatricula, + cpf, + coddiplomaafastamento, + codocorrencia, + datapublicacaoafastamento, + descdiplomaafastamento, + descocorrencia, + numerodiplomaafastamento, + datainicioferiasinterrompidas, + diasrestantes + from {{ source("siape", "dados_afastamento") }} + ), + + dados_afastamento_cleaned as ( + select + nullif( + trim(adiantamentosalarioferias), '' + ) as adiantamentosalarioferias_clean, + nullif(trim(anoexercicio), '') as anoexercicio_clean, + nullif(trim(datafim), '') as datafim_clean, + nullif(trim(datafimaquisicao), '') as datafimaquisicao_clean, + nullif(trim(dataini), '') as dataini_clean, + nullif(trim(datainicioaquisicao), '') as datainicioaquisicao_clean, + nullif(trim(gratificacaonatalina), '') as gratificacaonatalina_clean, + nullif(trim(numerodaparcela), '') as numerodaparcela_clean, + nullif( + trim(parcelacontinuacaointerrupcao), '' + ) as parcelacontinuacaointerrupcao_clean, + nullif(trim(parcelainterrompida), '') as parcelainterrompida_clean, + nullif(trim(qtdedias), '') as qtdedias_clean, + nullif(trim(grmatricula), '') as grmatricula_clean, + nullif(trim(cpf), '') as cpf_clean, + nullif(trim(coddiplomaafastamento), '') as coddiplomaafastamento_clean, + nullif(trim(codocorrencia), '') as codocorrencia_clean, + nullif( + trim(datapublicacaoafastamento), '' + ) as datapublicacaoafastamento_clean, + nullif(trim(descdiplomaafastamento), '') as descdiplomaafastamento_clean, + nullif(trim(descocorrencia), '') as descocorrencia_clean, + nullif(trim(numerodiplomaafastamento), '') as numerodiplomaafastamento_clean, + nullif( + trim(datainicioferiasinterrompidas), '' + ) as datainicioferiasinterrompidas_clean, + nullif(trim(diasrestantes), '') as diasrestantes_clean + from dados_afastamento_raw + ) + +select + adiantamentosalarioferias_clean as adiantamento_salario_ferias, + anoexercicio_clean as ano_exercicio, + -- adicionei esses checks pois tinham algumas strings de datas retornando "0" e + -- quebrando o to_date ... + case + when length(datafim_clean) = 8 then to_date(datafim_clean, 'DDMMYYYY') else null + end as dt_fim, + case + when length(datafimaquisicao_clean) = 8 + then to_date(datafimaquisicao_clean, 'DDMMYYYY') + else null + end as dt_fim_aquisicao, + case + when length(dataini_clean) = 8 then to_date(dataini_clean, 'DDMMYYYY') else null + end as dt_ini, + case + when length(datainicioaquisicao_clean) = 8 + then to_date(datainicioaquisicao_clean, 'DDMMYYYY') + else null + end as dt_inicio_aquisicao, + gratificacaonatalina_clean as gratificacao_natalina, + cast(numerodaparcela_clean as int) as numero_parcela, + parcelacontinuacaointerrupcao_clean as parcela_continuacao_interrupcao, + parcelainterrompida_clean as parcela_interrompida, + cast(qtdedias_clean as int) as qtde_dias, + grmatricula_clean as gr_matricula, + regexp_replace(cpf_clean, '[^0-9]', '', 'g') as cpf, + coddiplomaafastamento_clean as cod_diploma_afastamento, + codocorrencia_clean as cod_ocorrencia, + case + when length(datapublicacaoafastamento_clean) = 8 + then to_date(datapublicacaoafastamento_clean, 'YYYYMMDD') -- Essa veio diferente, n sei o pq + else null + end as dt_publicacao_afastamento, + descdiplomaafastamento_clean as desc_diploma_afastamento, + descocorrencia_clean as desc_ocorrencia, + cast(numerodiplomaafastamento_clean as int) as numero_diploma_afastamento, + case + when length(datainicioferiasinterrompidas_clean) = 8 + then to_date(datainicioferiasinterrompidas_clean, 'DDMMYYYY') + else null + end as dt_inicio_ferias_interrompidas, + cast(diasrestantes_clean as int) as dias_restantes +from dados_afastamento_cleaned diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_curriculo.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_curriculo.sql new file mode 100644 index 00000000..f73d779b --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_curriculo.sql @@ -0,0 +1,43 @@ +with + dados_curriculo_raw as ( + select + cpf, + identificunica, + codigo, + codcurso, + nomecurso, + dataconclusao, + instituicao, + nome, + cargahoraria, + cargo, + datainicio, + nomeorgaoempresa, + datafim, + projeto, + infoadicionais, + tipodesc + from {{ source("siape", "dados_curriculo") }} + ) + +select + regexp_replace(nullif(trim(cpf), ''), '[^0-9]', '', 'g') as cpf, + nullif(trim(identificunica), '') as ident_unica, + nullif(trim(codigo), '') as codigo_experiencia, + nullif(trim(codcurso), '') as cod_curso, + nullif(trim(nomecurso), '') as nome_curso, + -- Converte YYYYMM para DATE (1º dia do mês) + to_date(nullif(trim(dataconclusao), '') || '01', 'YYYYMMDD') as dt_mes_conclusao, + nullif(trim(instituicao), '') as nome_instituicao, + nullif(trim(nome), '') as nome_area_experiencia, + nullif(trim(cargahoraria), '') as carga_horaria, -- Mantido como varchar para segurança + nullif(trim(cargo), '') as nome_cargo, + -- Converte YYYYMM para DATE (1º dia do mês) + to_date(nullif(trim(datainicio), '') || '01', 'YYYYMMDD') as dt_mes_inicio, + nullif(trim(nomeorgaoempresa), '') as nome_orgao_empresa, + -- Converte YYYYMM para DATE (1º dia do mês) + to_date(nullif(trim(datafim), '') || '01', 'YYYYMMDD') as dt_mes_fim, + nullif(trim(projeto), '') as descricao_projeto, + nullif(trim(infoadicionais), '') as informacoes_adicionais, + nullif(trim(tipodesc), '') as tipo_descricao +from dados_curriculo_raw diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_dependentes.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_dependentes.sql new file mode 100644 index 00000000..a4f7954e --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_dependentes.sql @@ -0,0 +1,50 @@ +with + dados_dependentes_raw as ( + select + codcondicao, + codgrauparentesco, + codorgao, + cpf, + matricula, + nome, + nomecondicao, + nomegrauparentesco, + codbeneficio, + datafim, + datainicio, + nomebeneficio + from {{ source("siape", "dados_dependentes") }} + ), + + dados_dependentes_cleaned as ( + select + nullif(trim(codcondicao), 'NaN') as codcondicao, + nullif(trim(codgrauparentesco), 'NaN') as codgrauparentesco, + nullif(trim(codorgao), 'NaN') as codorgao, + nullif(trim(cpf), 'NaN') as cpf, + nullif(trim(matricula), 'NaN') as matricula, + nullif(trim(nome), 'NaN') as nome, + nullif(trim(nomecondicao), 'NaN') as nomecondicao, + nullif(trim(nomegrauparentesco), 'NaN') as nomegrauparentesco, + nullif(trim(codbeneficio), 'NaN') as codbeneficio, + nullif(trim(datafim), 'NaN') as datafim, + nullif(trim(datainicio), 'NaN') as datainicio, + nullif(trim(nomebeneficio), 'NaN') as nomebeneficio + from dados_dependentes_raw + ) + +select + nullif(codcondicao, '') as cod_condicao, + nullif(codgrauparentesco, '') as cod_grau_parentesco, + nullif(codorgao, '') as cod_orgao, + regexp_replace(nullif(cpf, ''), '[^0-9]', '', 'g') as cpf, + nullif(matricula, '') as matricula, + nullif(nome, '') as nome_dependente, + nullif(nomecondicao, '') as nome_condicao, + nullif(nomegrauparentesco, '') as nome_grau_parentesco, + nullif(codbeneficio, '') as cod_beneficio, + -- Converte para DATE, tratando '', 'NaN' e '00000000' como NULL + to_date(nullif(nullif(datafim, ''), '00000000'), 'DDMMYYYY') as dt_fim, + to_date(nullif(nullif(datainicio, ''), '00000000'), 'DDMMYYYY') as dt_inicio, + nullif(nomebeneficio, '') as nome_beneficio +from dados_dependentes_cleaned diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_escolares.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_escolares.sql new file mode 100644 index 00000000..c19aef12 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_escolares.sql @@ -0,0 +1,26 @@ +with + dados_escolares_raw as ( + select + codcurso, + nomecurso, + codmatricula, + codorgao, + codtitulacao, + nometitulacao, + codescolaridade, + nomeescolaridade, + cpf + from {{ source("siape", "dados_escolares") }} + ) + +select + nullif(trim(codcurso), '') as cod_curso, + nullif(trim(nomecurso), '') as nome_curso, + nullif(trim(codmatricula), '') as cod_matricula, + nullif(trim(codorgao), '') as cod_orgao, + nullif(trim(codtitulacao), '') as cod_titulacao, + nullif(trim(nometitulacao), '') as nome_titulacao, + nullif(trim(codescolaridade), '') as cod_escolaridade, + nullif(trim(nomeescolaridade), '') as nome_escolaridade, + regexp_replace(nullif(trim(cpf), ''), '[^0-9]', '', 'g') as cpf +from dados_escolares_raw diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_financeiros.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_financeiros.sql new file mode 100644 index 00000000..84f38deb --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_financeiros.sql @@ -0,0 +1,123 @@ +with + dados_financeiros_raw as ( + select + codrubrica, + indicadorrd, + nomerubrica, + numeroseq, + valorrubrica, + dataanomesrubrica, + pzrubrica, + mesanopagamento, + cpf, + indicadormovsupl, + perubrica + from {{ source("siape", "dados_financeiros") }} + ), + + dados_financeiros_cleaned as ( + select + nullif(trim(codrubrica), '') as cod_rubrica, + nullif(trim(indicadorrd), '') as indicador_rd, + nullif(trim(nomerubrica), '') as nome_rubrica, + nullif(trim(numeroseq), '') as numero_sequencia, + nullif(trim(valorrubrica), '') as valor_rubrica_str, -- Mantém como string + nullif(trim(dataanomesrubrica), '') as data_anomes_rubrica_str, + nullif(trim(pzrubrica), '') as prazo_rubrica, + nullif(trim(mesanopagamento), '') as mes_ano_pagamento_str, + nullif(trim(cpf), '') as cpf_str, + nullif(trim(indicadormovsupl), '') as indicador_mov_supl, + nullif(trim(perubrica), '') as periodo_rubrica + from dados_financeiros_raw + ), + + conversao_mes as ( + select + *, + case + upper(substring(data_anomes_rubrica_str, 1, 3)) + when 'JAN' + then '01' + when 'FEV' + then '02' + when 'MAR' + then '03' + when 'ABR' + then '04' + when 'MAI' + then '05' + when 'JUN' + then '06' + when 'JUL' + then '07' + when 'AGO' + then '08' + when 'SET' + then '09' + when 'OUT' + then '10' + when 'NOV' + then '11' + when 'DEZ' + then '12' + else null + end as mes_num_rubrica, + substring(data_anomes_rubrica_str, 4, 4) as ano_rubrica, + case + upper(substring(mes_ano_pagamento_str, 1, 3)) + when 'JAN' + then '01' + when 'FEV' + then '02' + when 'MAR' + then '03' + when 'ABR' + then '04' + when 'MAI' + then '05' + when 'JUN' + then '06' + when 'JUL' + then '07' + when 'AGO' + then '08' + when 'SET' + then '09' + when 'OUT' + then '10' + when 'NOV' + then '11' + when 'DEZ' + then '12' + else null + end as mes_num_pagamento, + substring(mes_ano_pagamento_str, 4, 4) as ano_pagamento + from dados_financeiros_cleaned + ) + +select + cod_rubrica, + indicador_rd, + nome_rubrica, + numero_sequencia, + -- Limpa (remove '.') e converte (',' para '.') para NUMERIC + cast( + replace( + replace(valor_rubrica_str, '.', ''), -- Remove o separador de milhares '.' + ',', + '.' -- Troca a vírgula decimal ',' por '.' + ) as numeric + ) as valor_rubrica, + -- Converte MONYYYY para DATE (primeiro dia do mês) + to_date( + ano_rubrica || '-' || mes_num_rubrica || '-01', 'YYYY-MM-DD' + ) as data_anomes_rubrica, + prazo_rubrica, + -- Converte MONYYYY para DATE (primeiro dia do mês) + to_date( + ano_pagamento || '-' || mes_num_pagamento || '-01', 'YYYY-MM-DD' + ) as mes_ano_pagamento, + regexp_replace(cpf_str, '[^0-9]', '', 'g') as cpf, + indicador_mov_supl, + periodo_rubrica +from conversao_mes diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_funcionais.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_funcionais.sql new file mode 100644 index 00000000..828c78ae --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_funcionais.sql @@ -0,0 +1,86 @@ +with dados_funcionais_raw as (select * from {{ source("siape", "dados_funcionais") }}) + +select + nullif(trim(codativfun), '') as cod_atividade_funcao, + nullif(trim(codfuncao), '') as cod_funcao, + nullif(trim(codjornada), '') as cod_jornada, + nullif(trim(codocorringressoorgao), '') as cod_ocorr_ingresso_orgao, + nullif(trim(codocorringressoservpublico), '') as cod_ocorr_ingresso_serv_publico, + nullif(trim(codorgao), '') as cod_orgao, + nullif(trim(codpadrao), '') as cod_padrao, + nullif(trim(codsitfuncional), '') as cod_situacao_funcional, + nullif(trim(coduorgexercicio), '') as cod_uorg_exercicio, + nullif(trim(codupag), '') as cod_upag, + nullif(trim(codigoorgaoorigem), '') as cod_orgao_origem, + regexp_replace( + nullif(trim(cpfchefiaimediata), ''), '[^0-9]', '', 'g' + ) as cpf_chefia_imediata, + to_date(nullif(trim(dataexercicionoorgao), ''), 'DDMMYYYY') as dt_exercicio_no_orgao, + to_date(nullif(trim(datafimvalear), ''), 'DDMMYYYY') as dt_fim_vale_ar, + to_date(nullif(trim(dataingressofuncao), ''), 'DDMMYYYY') as dt_ingresso_funcao, + to_date( + nullif(trim(dataocorringressoorgao), ''), 'DDMMYYYY' + ) as dt_ocorr_ingresso_orgao, + to_date( + nullif(trim(dataocorringressoservpublico), ''), 'DDMMYYYY' + ) as dt_ocorr_ingresso_serv_publico, + lower(nullif(trim(emailchefiaimediata), '')) as email_chefia_imediata, + lower(nullif(trim(emailinstitucional), '')) as email_institucional, + lower(nullif(trim(emailservidor), '')) as email_servidor, + nullif(trim(identunica), '') as ident_unica, + nullif(trim(matriculasiape), '') as matricula_siape, + nullif(trim(modalidadepgd), '') as modalidade_pgd, + nullif(trim(nomeativfun), '') as nome_atividade_funcao, + nullif(trim(nomechefeuorg), '') as nome_chefe_uorg, + nullif(trim(nomefuncao), '') as nome_funcao, + nullif(trim(nomejornada), '') as nome_jornada, + nullif(trim(nomeocorringressoorgao), '') as nome_ocorr_ingresso_orgao, + nullif(trim(nomeocorringressoservpublico), '') as nome_ocorr_ingresso_serv_publico, + nullif(trim(nomeorgao), '') as nome_orgao, + nullif(trim(nomeregimejuridico), '') as nome_regime_juridico, + nullif(trim(nomesitfuncional), '') as nome_situacao_funcional, + nullif(trim(nomeuorgexercicio), '') as nome_uorg_exercicio, + nullif(trim(nomeupag), '') as nome_upag, + nullif(trim(participapgd), '') as participa_pgd, + cast(replace(nullif(trim(percentualts), ''), ',', '.') as numeric) + / 100 as percentual_ts, + nullif(trim(siglaorgao), '') as sigla_orgao, + nullif(trim(siglaorgaoorigem), '') as sigla_orgao_origem, + nullif(trim(siglaregimejuridico), '') as sigla_regime_juridico, + nullif(trim(siglauorgexercicio), '') as sigla_uorg_exercicio, + nullif(trim(siglaupag), '') as sigla_upag, + regexp_replace(nullif(trim(cpf), ''), '[^0-9]', '', 'g') as cpf, + nullif(trim(codcargo), '') as cod_cargo, + nullif(trim(codclasse), '') as cod_classe, + nullif(trim(codocorraposentadoria), '') as cod_ocorr_aposentadoria, + to_date(nullif(trim(datainivalear), ''), 'DDMMYYYY') as dt_ini_vale_ar, + to_date( + nullif(trim(dataocorraposentadoria), ''), 'DDMMYYYY' + ) as dt_ocorr_aposentadoria, + nullif(trim(nomecargo), '') as nome_cargo, + nullif(trim(nomeclasse), '') as nome_classe, + nullif(trim(nomeocorraposentadoria), '') as nome_ocorr_aposentadoria, + nullif(trim(siglanivelcargo), '') as sigla_nivel_cargo, + nullif(trim(tipovalear), '') as tipo_vale_ar, + nullif(trim(codocorrisencaoir), '') as cod_ocorr_isencao_ir, + to_date( + nullif(trim(datainiocorrisencaoir), ''), 'DDMMYYYY' + ) as dt_ini_ocorr_isencao_ir, + nullif(trim(nomeocorrisencaoir), '') as nome_ocorr_isencao_ir, + nullif(trim(coduorglotacao), '') as cod_uorg_lotacao, + nullif(trim(nomeuorglotacao), '') as nome_uorg_lotacao, + nullif(trim(siglauorglotacao), '') as sigla_uorg_lotacao, + to_date( + nullif(trim(datafimocorrisencaoir), ''), 'DDMMYYYY' + ) as dt_fim_ocorr_isencao_ir, + nullif(trim(codocorrexclusao), '') as cod_ocorr_exclusao, + to_date(nullif(trim(dataocorrexclusao), ''), 'DDMMYYYY') as dt_ocorr_exclusao, + nullif(trim(nomeocorrexclusao), '') as nome_ocorr_exclusao, + to_date(nullif(trim(datauorglotacao), ''), 'DDMMYYYY') as dt_uorg_lotacao, + nullif(trim(codvaletransporte), '') as cod_vale_transporte, + cast( + replace(nullif(trim(valorvaletransporte), ''), ',', '.') as numeric + ) as valor_vale_transporte, + to_date(nullif(trim(datauorgexercicio), ''), 'DDMMYYYY') as dt_uorg_exercicio, + nullif(trim(pontuacaodesempenho), '') as pontuacao_desempenho -- Mantido como varchar (Não da pra saber se é "A","B" ou é um número > tudo null) +from dados_funcionais_raw diff --git a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_pa.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_pa.sql similarity index 64% rename from airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_pa.sql rename to airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_pa.sql index c4915c80..32676ccc 100644 --- a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_pa.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_pa.sql @@ -16,6 +16,31 @@ with from {{ source("siape", "dados_pa") }} ) + << << + << < head + == == + == = + +with + dados_pa as ( + select + agenciabeneficiario, + bancobeneficiario, + codorgao, + contabeneficiario, + cpfbeneficiario, + matricula, + nomebeneficiario, + valorultimapensao, + cpf_servidor, + codvinculoservidor, + nomealimentado, + nomevinculoservidor + from {{ source("siape", "dados_pa") }} + ) + + >> >> + >> > 2884731 (changed tenant siape dbt to pessoas dbt) select regexp_replace( nullif(trim(agenciabeneficiario), ''), '[^0-9]', '', 'g' @@ -31,7 +56,11 @@ select nullif(trim(matricula), '') as matricula_servidor, nullif(trim(nomebeneficiario), '') as nome_beneficiario, nullif(trim(valorultimapensao), '') as valor_ultima_pensao, + << << << < head regexp_replace(nullif(trim(cpf), ''), '[^0-9]', '', 'g') as cpf_servidor, + == == == = + regexp_replace(nullif(trim(cpf_servidor), ''), '[^0-9]', '', 'g') as cpf_servidor, + >> >> >> > 2884731 (changed tenant siape dbt to pessoas dbt) nullif(trim(codvinculoservidor), '') as cod_vinculo_servidor, nullif(trim(nomealimentado), '') as nome_alimentado, nullif(trim(nomevinculoservidor), '') as nome_vinculo_servidor diff --git a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_pessoais.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_pessoais.sql similarity index 100% rename from airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_pessoais.sql rename to airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_pessoais.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_uorg.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_uorg.sql new file mode 100644 index 00000000..a0edb35b --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_uorg.sql @@ -0,0 +1,97 @@ +<< << << < head +with + dados_uorg as ( + select + bairrouorg, + cepuorg, + codmatricula, + codmunicipiouorg, + codorgao, + codorgaouorg, + emailuorg, + enduorg, + logradourouorg, + nomemunicipiouorg, + nomeuorg, + numtelefoneuorg, + numerouorg, + siglauorg, + ufuorg, + cpf, + complementouorg, + numfaxuorg + from {{ source("siape", "dados_uorg") }} + ) + == == + == = +with + dados_uorg as ( + select + bairrouorg, + cepuorg, + codmatricula, + codmunicipiouorg, + codorgao, + codorgaouorg, + emailuorg, + enduorg, + logradourouorg, + nomemunicipiouorg, + nomeuorg, + numtelefoneuorg, + numerouorg, + siglauorg, + ufuorg, + cpf, + complementouorg, + numfaxuorg + from {{ source("siape", "dados_uorg") }} + ) + >> >> + >> > b2eef63( + created afastamento_consolidado + and servidores_detalhados tables at siape silver tenant + ) + + << << + << < head +select + nullif(trim(bairrouorg), '') as bairro_uorg, + regexp_replace(nullif(trim(cepuorg), ''), '[^0-9]', '', 'g') as cep_uorg, + nullif(trim(codmatricula), '') as codigo_matricula, + nullif(trim(codmunicipiouorg), '') as codigo_municipio_uorg, + nullif(trim(codorgao), '') as codigo_orgao, + nullif(trim(codorgaouorg), '') as codigo_orgao_u + lower(nullif(trim(emailuorg), '')) as email_uorg, + nullif(trim(enduorg), '') as tipo_endereco_uorg, + nullif(trim(logradourouorg), '') as logradouro_uorg, + nullif(trim(nomemunicipiouorg), '') as nome_municipio_uorg, + nullif(trim(nomeuorg), '') as nome_uorg, + regexp_replace(nullif(trim(numtelefoneuorg), ''), '[^0-9]', '', 'g') as telefone_uorg, + nullif(trim(numerouorg), '') as numero_endereco_uorg, + nullif(trim(siglauorg), '') as sigla_uorg, + upper(nullif(trim(ufuorg), '')) as uf_uorg, + regexp_replace(nullif(trim(cpf), ''), '[^0-9]', '', 'g') as cpf, + nullif(nullif(trim(complementouorg), ''), '---') as complemento_endereco_uorg, + regexp_replace(nullif(trim(numfaxuorg), ''), '[^0-9]', '', 'g') as fax_uorg +from dados_uorg == == == = +select + nullif(trim(bairrouorg), '') as bairro_uorg, + regexp_replace(nullif(trim(cepuorg), ''), '[^0-9]', '', 'g') as cep_uorg, + nullif(trim(codmatricula), '') as codigo_matricula, + nullif(trim(codmunicipiouorg), '') as codigo_municipio_uorg, + nullif(trim(codorgao), '') as codigo_orgao, + nullif(trim(codorgaouorg), '') as codigo_orgao_uorg, + lower(nullif(trim(emailuorg), '')) as email_uorg, + nullif(trim(enduorg), '') as tipo_endereco_uorg, + nullif(trim(logradourouorg), '') as logradouro_uorg, + nullif(trim(nomemunicipiouorg), '') as nome_municipio_uorg, + nullif(trim(nomeuorg), '') as nome_uorg, + regexp_replace(nullif(trim(numtelefoneuorg), ''), '[^0-9]', '', 'g') as telefone_uorg, + nullif(trim(numerouorg), '') as numero_endereco_uorg, + nullif(trim(siglauorg), '') as sigla_uorg, + upper(nullif(trim(ufuorg), '')) as uf_uorg, + regexp_replace(nullif(trim(cpf), ''), '[^0-9]', '', 'g') as cpf, + nullif(nullif(trim(complementouorg), ''), '---') as complemento_endereco_uorg, + regexp_replace(nullif(trim(numfaxuorg), ''), '[^0-9]', '', 'g') as fax_uorg +from dados_uorg >> >> >> > 2884731 (changed tenant siape dbt to pessoas dbt) diff --git a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/lista_servidores.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_servidores.sql similarity index 51% rename from airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/lista_servidores.sql rename to airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_servidores.sql index bcc0c7f4..788db43c 100644 --- a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/lista_servidores.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_servidores.sql @@ -1,3 +1,4 @@ +<< << << < head with lista_servidores as ( select @@ -6,6 +7,15 @@ with coduorg as cod_uorg from {{ source("siape", "lista_servidores") }} ) + == == + == = +with + lista_servidores as ( + select cpf, dt_ultima_transacao, cod_uorg + from {{ source("siape", "lista_servidores") }} + ) + >> >> + >> > 2884731 (changed tenant siape dbt to pessoas dbt) select * from lista_servidores diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_uorgs.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_uorgs.sql new file mode 100644 index 00000000..c43af9c3 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_uorgs.sql @@ -0,0 +1,21 @@ +with + lista_uorgs as ( + select + cast(codigo as int) as codigo, + to_date(dataultimatransacao, 'DDMMYYYY') as dt_ultima_transacao, + nome + from {{ source("siape", "lista_uorgs") }} + ) + + << << + << < head +select * +from lista_uorgs == == == = +with + lista_uorgs as ( + select cast(codigo as int) as codigo, dt_ultima_transacao, nome + from {{ source("siape", "lista_uorgs") }} + ) + +select * +from lista_uorgs >> >> >> > 2884731 (changed tenant siape dbt to pessoas dbt) diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/cargos_consolidado.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/cargos_consolidado.sql new file mode 100644 index 00000000..c215f4bb --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/cargos_consolidado.sql @@ -0,0 +1,29 @@ +-- cargos siape + siorg +-- ver logica para achar os cargos vagos e mudar essa tabela ! +-- como o siorg lista todos os cargos, os que vierem null no siape provavelmente estão +-- vagos +select + siorg.codigounidade as siorg_cod_unidade, + siorg.nomeunidade as siorg_nome_unidade, + siorg.siglaunidade as siorg_sigla_unidade, + siorg.municipio as siorg_municipio_unidade, + siorg.uf as siorg_uf_unidade, + siorg.denominacao as siorg_denominacao_cargo, + siorg.funcao as siorg_funcao, + siorg.codigo_instancia as siorg_cod_instancia_cargo, + siorg.cpf_titular as siorg_cpf_titular, + siorg.nome_titular as siorg_nome_titular, + + siape.cpf as siape_cpf, + siape.nome_pessoa as siape_nome_pessoa, + siape.nome_cargo as siape_nome_cargo_efetivo, + siape.nome_funcao as siape_nome_funcao_comissionada, + siape.cod_uorg_exercicio as siape_cod_uorg, + siape.nome_uorg_exercicio as siape_nome_uorg, + siape.sigla_uorg_exercicio as siape_sigla_uorg, + siape.uf_uorg as siape_uf_uorg, + siape.nome_situacao_funcional as siape_situacao_funcional + +from {{ ref("servidores_detalhados") }} siape +left join + {{ ref("estrutura_organizacional_cargos") }} siorg on siape.cpf = siorg.cpf_titular -- tabela siorg diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/resumo_quadro_pessoal.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/resumo_quadro_pessoal.sql new file mode 100644 index 00000000..553259bb --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/resumo_quadro_pessoal.sql @@ -0,0 +1,8 @@ +select + coalesce(nome_cargo, 'N/A') as cargo_efetivo, + coalesce(nome_sexo, 'N/A') as genero, + coalesce(nome_situacao_funcional, 'N/A') as situacao_funcional, + coalesce(uf_uorg, 'N/A') as localidade_uf, + count(distinct cpf) as quantidade_servidores +from {{ ref("servidores_detalhados") }} +group by 1, 2, 3, 4 diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/afastamento_consolidado.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/afastamento_consolidado.sql new file mode 100644 index 00000000..0e7c29ee --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/afastamento_consolidado.sql @@ -0,0 +1,51 @@ +select + adiantamento_salario_ferias, + ano_exercicio, + dt_fim, + dt_fim_aquisicao, + dt_ini, + dt_inicio_aquisicao, + dt_inicio_ferias_interrompidas, + dias_restantes, + gratificacao_natalina, + numero_parcela, + parcela_continuacao_interrupcao, + parcela_interrompida, + qtde_dias, + cpf, + cod_diploma_afastamento, + cod_ocorrencia, + dt_publicacao_afastamento, + desc_diploma_afastamento, + desc_ocorrencia, + numero_diploma_afastamento, + gr_matricula, + 'dados_afastamento' as origem_dados -- identificar a fonte +from {{ ref("dados_afastamento") }} + +union all + +select + adiantamento_salario_ferias, + ano_exercicio, + dt_fim, + dt_fim_aquisicao, + dt_ini, + dt_inicio_aquisicao, + dt_inicio_ferias_interrompidas, + dias_restantes, + gratificacao_natalina, + numero_parcela, + parcela_continuacao_interrupcao, + parcela_interrompida, + qtde_dias, + cpf, + cod_diploma_afastamento, + cod_ocorrencia, + dt_publicacao_afastamento, + desc_diploma_afastamento, + desc_ocorrencia, + numero_diploma_afastamento, + null as gr_matricula, -- não tem na afastamneto historico ... + 'afastamento_historico' as origem_dados -- identificar a fonte +from {{ ref("afastamento_historico") }} diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/servidores_detalhados.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/servidores_detalhados.sql new file mode 100644 index 00000000..2c6b5f23 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/servidores_detalhados.sql @@ -0,0 +1,151 @@ +with + educacao_principal as ( + select cpf, cod_escolaridade, nome_escolaridade, cod_titulacao, nome_titulacao + from {{ ref("dados_escolares") }} + ), + uorg_completo as ( + select + du.bairro_uorg, + du.cep_uorg, + du.codigo_matricula, + du.codigo_municipio_uorg, + du.codigo_orgao, + du.codigo_orgao_uorg, + du.email_uorg, + du.tipo_endereco_uorg, + du.logradouro_uorg, + du.nome_municipio_uorg, + du.nome_uorg, + du.telefone_uorg, + du.numero_endereco_uorg, + du.sigla_uorg, + du.uf_uorg, + du.cpf, + du.complemento_endereco_uorg, + du.fax_uorg + -- lu.dt_ultima_transacao AS dt_ultima_transacao_uorg, os codigos não batem e a + -- informação aparentemente ja existe... + -- lu.nome AS nome_uorg_lista + from {{ ref("dados_uorg") }} du + -- LEFT JOIN {{ ref('lista_uorgs') }} lu + ) +select + dp.cpf, + dp.nome_pessoa, + dp.dt_nascimento, + dp.grupo_sanguineo, + dp.nome_cor, + dp.cod_cor, + dp.nome_estado_civil, + dp.cod_estado_civil, + dp.nome_mae, + dp.nome_pai, + dp.nome_municipio_nascimento, + dp.nome_nacionalidade, + dp.cod_nacionalidade, + dp.nome_sexo, + dp.cod_sexo, + dp.num_pispasep, + dp.uf_nascimento, + dp.cod_deficiencia_fisica, + dp.nome_deficiencia_fisica, + dp.dt_chegada_brasil, + dp.nome_pais_origem, + + df.cod_atividade_funcao, + df.cod_funcao, + df.cod_jornada, + df.cod_ocorr_ingresso_orgao, + df.cod_ocorr_ingresso_serv_publico, + df.cod_orgao as cod_orgao_funcional, + df.cod_padrao, + df.cod_situacao_funcional, + df.cod_uorg_exercicio, + df.cod_upag, + df.cod_orgao_origem, + df.cpf_chefia_imediata, + df.dt_exercicio_no_orgao, + df.dt_fim_vale_ar, + df.dt_ingresso_funcao, + df.dt_ocorr_ingresso_orgao, + df.dt_ocorr_ingresso_serv_publico, + df.email_chefia_imediata, + df.email_institucional, + df.email_servidor, + df.ident_unica as ident_unica_funcional, + df.matricula_siape, + df.modalidade_pgd, + df.nome_atividade_funcao, + df.nome_chefe_uorg, + df.nome_funcao, + df.nome_jornada, + df.nome_ocorr_ingresso_orgao, + df.nome_ocorr_ingresso_serv_publico, + df.nome_orgao as nome_orgao_funcional, + df.nome_regime_juridico, + df.nome_situacao_funcional, + df.nome_uorg_exercicio, + df.nome_upag, + df.participa_pgd, + df.percentual_ts, + df.sigla_orgao as sigla_orgao_funcional, + df.sigla_orgao_origem, + df.sigla_regime_juridico, + df.sigla_uorg_exercicio, + df.sigla_upag, + df.cod_cargo, + df.cod_classe, + df.cod_ocorr_aposentadoria, + df.dt_ini_vale_ar, + df.dt_ocorr_aposentadoria, + df.nome_cargo, + df.nome_classe, + df.nome_ocorr_aposentadoria, + df.sigla_nivel_cargo, + df.tipo_vale_ar, + df.cod_ocorr_isencao_ir, + df.dt_ini_ocorr_isencao_ir, + df.nome_ocorr_isencao_ir, + df.cod_uorg_lotacao, + df.nome_uorg_lotacao, + df.sigla_uorg_lotacao, + df.dt_fim_ocorr_isencao_ir, + df.cod_ocorr_exclusao, + df.dt_ocorr_exclusao, + df.nome_ocorr_exclusao, + df.dt_uorg_lotacao, + df.cod_vale_transporte, + df.valor_vale_transporte, + df.dt_uorg_exercicio, + df.pontuacao_desempenho, + + ep.nome_escolaridade as nome_escolaridade_principal, + ep.cod_escolaridade as cod_escolaridade_principal, + ep.nome_titulacao as nome_titulacao_principal, + ep.cod_titulacao as cod_titulacao_principal, + + ls.dt_ultima_transacao as dt_ultima_transacao_servidor, + + uorg_c.bairro_uorg, + uorg_c.cep_uorg, + uorg_c.codigo_matricula, + uorg_c.codigo_municipio_uorg, + uorg_c.codigo_orgao, + uorg_c.codigo_orgao_uorg, + uorg_c.email_uorg, + uorg_c.tipo_endereco_uorg, + uorg_c.logradouro_uorg, + uorg_c.nome_municipio_uorg, + uorg_c.nome_uorg, + uorg_c.telefone_uorg, + uorg_c.numero_endereco_uorg, + uorg_c.sigla_uorg, + uorg_c.uf_uorg, + uorg_c.complemento_endereco_uorg, + uorg_c.fax_uorg + +from {{ ref("dados_pessoais") }} dp +left join {{ ref("dados_funcionais") }} df on dp.cpf = df.cpf +left join educacao_principal ep on dp.cpf = ep.cpf +left join {{ ref("lista_servidores") }} ls on dp.cpf = ls.cpf +left join uorg_completo uorg_c on dp.cpf = uorg_c.cpf diff --git a/airflow_lappis/dags/dbt/ipea/models/schema.yml b/airflow_lappis/dags/dbt/ipea/models/schema.yml index 9b15d42b..f2991f62 100644 --- a/airflow_lappis/dags/dbt/ipea/models/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/schema.yml @@ -788,6 +788,417 @@ models: description: > Diferença entre o valor programado no cronograma e a soma dos valores faturados (pagos e pendentes). + #Siape + - name: servidores_detalhados + description: > + Esta tabela consolida informações detalhadas sobre os servidores, combinando dados pessoais, funcionais, + educacionais (uma das formações ou todas, dependendo da cardinalidade da junção com dados_escolares), + informações da última transação e detalhes da UORG associada ao servidor. + Serve como uma base rica para análises e relatórios sobre o quadro de pessoal. + columns: + - name: cpf + description: > + CPF do servidor, utilizado como chave primária para junção de dados. + - name: nome_pessoa + description: > + Nome completo do servidor. + - name: dt_nascimento + description: > + Data de nascimento do servidor. + - name: grupo_sanguineo + description: > + Grupo sanguíneo do servidor. + - name: nome_cor + description: > + Nome da cor ou raça declarada pelo servidor. + - name: cod_cor + description: > + Código da cor ou raça declarada pelo servidor. + - name: nome_estado_civil + description: > + Nome do estado civil do servidor. + - name: cod_estado_civil + description: > + Código do estado civil do servidor. + - name: nome_mae + description: > + Nome da mãe do servidor. + - name: nome_pai + description: > + Nome do pai do servidor. + - name: nome_municipio_nascimento + description: > + Nome do município de nascimento do servidor. + - name: nome_nacionalidade + description: > + Nome da nacionalidade do servidor. + - name: cod_nacionalidade + description: > + Código da nacionalidade do servidor. + - name: nome_sexo + description: > + Nome do sexo declarado pelo servidor. + - name: cod_sexo + description: > + Código do sexo declarado pelo servidor. + - name: num_pispasep + description: > + Número do PIS/PASEP do servidor. + - name: uf_nascimento + description: > + UF (Unidade Federativa) de nascimento do servidor. + - name: cod_deficiencia_fisica + description: > + Código da deficiência física do servidor, se houver. + - name: nome_deficiencia_fisica + description: > + Nome da deficiência física do servidor, se houver. + - name: dt_chegada_brasil + description: > + Data de chegada ao Brasil, para servidores estrangeiros. + - name: nome_pais_origem + description: > + Nome do país de origem, para servidores estrangeiros. + - name: cod_atividade_funcao + description: > + Código da atividade da função desempenhada pelo servidor. + - name: cod_funcao + description: > + Código da função ou cargo comissionado do servidor. + - name: cod_jornada + description: > + Código da jornada de trabalho do servidor. + - name: cod_ocorr_ingresso_orgao + description: > + Código da ocorrência de ingresso do servidor no órgão. + - name: cod_ocorr_ingresso_serv_publico + description: > + Código da ocorrência de ingresso do servidor no serviço público. + - name: cod_orgao_funcional + description: > + Código do órgão funcional do servidor. + - name: cod_padrao + description: > + Código do padrão do cargo do servidor. + - name: cod_situacao_funcional + description: > + Código da situação funcional do servidor. + - name: cod_uorg_exercicio + description: > + Código da UORG (Unidade Organizacional) de exercício do servidor. + - name: cod_upag + description: > + Código da UPAG (Unidade Pagadora) do servidor. + - name: cod_orgao_origem + description: > + Código do órgão de origem do servidor. + - name: cpf_chefia_imediata + description: > + CPF da chefia imediata do servidor. + - name: dt_exercicio_no_orgao + description: > + Data de início do exercício do servidor no órgão. + - name: dt_fim_vale_ar + description: > + Data de término do Vale AR (contexto específico do SIAPE). + - name: dt_ingresso_funcao + description: > + Data de ingresso do servidor na função ou cargo comissionado. + - name: dt_ocorr_ingresso_orgao + description: > + Data da ocorrência de ingresso do servidor no órgão. + - name: dt_ocorr_ingresso_serv_publico + description: > + Data da ocorrência de ingresso do servidor no serviço público. + - name: email_chefia_imediata + description: > + Email da chefia imediata do servidor. + - name: email_institucional + description: > + Email institucional do servidor. + - name: email_servidor + description: > + Email pessoal do servidor (registrado no sistema). + - name: ident_unica_funcional + description: > + Identificador único funcional do servidor. + - name: matricula_siape + description: > + Matrícula SIAPE do servidor. + - name: modalidade_pgd + description: > + Modalidade do PGD (Programa de Gestão de Desempenho) do servidor. + - name: nome_atividade_funcao + description: > + Nome da atividade da função desempenhada pelo servidor. + - name: nome_chefe_uorg + description: > + Nome do chefe da UORG do servidor. + - name: nome_funcao + description: > + Nome da função ou cargo comissionado do servidor. + - name: nome_jornada + description: > + Nome da jornada de trabalho do servidor. + - name: nome_ocorr_ingresso_orgao + description: > + Nome da ocorrência de ingresso do servidor no órgão. + - name: nome_ocorr_ingresso_serv_publico + description: > + Nome da ocorrência de ingresso do servidor no serviço público. + - name: nome_orgao_funcional + description: > + Nome do órgão funcional do servidor. + - name: nome_regime_juridico + description: > + Nome do regime jurídico do servidor. + - name: nome_situacao_funcional + description: > + Nome da situação funcional do servidor. + - name: nome_uorg_exercicio + description: > + Nome da UORG de exercício do servidor. + - name: nome_upag + description: > + Nome da UPAG (Unidade Pagadora) do servidor. + - name: participa_pgd + description: > + Indicador se o servidor participa do PGD. + - name: percentual_ts + description: > + Percentual de Tempo de Serviço. + - name: sigla_orgao_funcional + description: > + Sigla do órgão funcional do servidor. + - name: sigla_orgao_origem + description: > + Sigla do órgão de origem do servidor. + - name: sigla_regime_juridico + description: > + Sigla do regime jurídico do servidor. + - name: sigla_uorg_exercicio + description: > + Sigla da UORG de exercício do servidor. + - name: sigla_upag + description: > + Sigla da UPAG (Unidade Pagadora) do servidor. + - name: cod_cargo + description: > + Código do cargo efetivo do servidor. + - name: cod_classe + description: > + Código da classe do cargo do servidor. + - name: cod_ocorr_aposentadoria + description: > + Código da ocorrência de aposentadoria do servidor. + - name: dt_ini_vale_ar + description: > + Data de início do Vale AR (contexto específico do SIAPE). + - name: dt_ocorr_aposentadoria + description: > + Data da ocorrência de aposentadoria do servidor. + - name: nome_cargo + description: > + Nome do cargo efetivo do servidor. + - name: nome_classe + description: > + Nome da classe do cargo do servidor. + - name: nome_ocorr_aposentadoria + description: > + Nome da ocorrência de aposentadoria do servidor. + - name: sigla_nivel_cargo + description: > + Sigla do nível do cargo do servidor. + - name: tipo_vale_ar + description: > + Tipo do Vale AR (contexto específico do SIAPE). + - name: cod_ocorr_isencao_ir + description: > + Código da ocorrência de isenção de Imposto de Renda do servidor. + - name: dt_ini_ocorr_isencao_ir + description: > + Data de início da ocorrência de isenção de IR do servidor. + - name: nome_ocorr_isencao_ir + description: > + Nome da ocorrência de isenção de IR do servidor. + - name: cod_uorg_lotacao + description: > + Código da UORG de lotação do servidor. + - name: nome_uorg_lotacao + description: > + Nome da UORG de lotação do servidor. + - name: sigla_uorg_lotacao + description: > + Sigla da UORG de lotação do servidor. + - name: dt_fim_ocorr_isencao_ir + description: > + Data de fim da ocorrência de isenção de IR do servidor. + - name: cod_ocorr_exclusao + description: > + Código da ocorrência de exclusão do servidor. + - name: dt_ocorr_exclusao + description: > + Data da ocorrência de exclusão do servidor. + - name: nome_ocorr_exclusao + description: > + Nome da ocorrência de exclusão do servidor. + - name: dt_uorg_lotacao + description: > + Data de movimentação para a UORG de lotação do servidor. + - name: cod_vale_transporte + description: > + Código do vale transporte do servidor. + - name: valor_vale_transporte + description: > + Valor do vale transporte do servidor. + - name: dt_uorg_exercicio + description: > + Data de movimentação para a UORG de exercício do servidor. + - name: pontuacao_desempenho + description: > + Pontuação de desempenho do servidor. + - name: nome_escolaridade_principal + description: > + Nome da escolaridade do servidor (proveniente de dados_escolares). + - name: cod_escolaridade_principal + description: > + Código da escolaridade do servidor (proveniente de dados_escolares). + - name: nome_titulacao_principal + description: > + Nome da titulação do servidor (proveniente de dados_escolares). + - name: cod_titulacao_principal + description: > + Código da titulação do servidor (proveniente de dados_escolares). + - name: dt_ultima_transacao_servidor + description: > + Data da última transação do servidor registrada no sistema SIAPE (proveniente de lista_servidores). + - name: bairro_uorg + description: > + Bairro da UORG associada ao servidor (proveniente de dados_uorg). + - name: cep_uorg + description: > + CEP da UORG associada ao servidor. + - name: codigo_matricula + description: > + Código de matrícula associado ao registro da UORG do servidor (contexto dados_uorg). + - name: codigo_municipio_uorg + description: > + Código do município da UORG associada ao servidor. + - name: codigo_orgao + description: > + Código do órgão da UORG associada ao servidor. + - name: codigo_orgao_uorg + description: > + Código da UORG (identificador da UORG em dados_uorg) associada ao servidor. + - name: email_uorg + description: > + Email da UORG associada ao servidor. + - name: tipo_endereco_uorg + description: > + Tipo de endereço da UORG associada ao servidor. + - name: logradouro_uorg + description: > + Logradouro da UORG associada ao servidor. + - name: nome_municipio_uorg + description: > + Nome do município da UORG associada ao servidor. + - name: nome_uorg + description: > + Nome da UORG associada ao servidor (conforme dados_uorg). + - name: telefone_uorg + description: > + Telefone da UORG associada ao servidor. + - name: numero_endereco_uorg + description: > + Número do endereço da UORG associada ao servidor. + - name: sigla_uorg + description: > + Sigla da UORG associada ao servidor (conforme dados_uorg). + - name: uf_uorg + description: > + UF da UORG associada ao servidor. + - name: complemento_endereco_uorg + description: > + Complemento do endereço da UORG associada ao servidor. + - name: fax_uorg + description: > + Fax da UORG associada ao servidor. + + - name: afastamento_consolidado + description: > + Esta tabela consolida todos os registros de afastamentos, licenças e férias dos servidores, + unindo dados atuais (de 'dados_afastamento') com dados históricos (de 'afastamento_historico'). + Inclui detalhes sobre os períodos, tipos de ocorrência, amparo legal e informações financeiras relacionadas, como adiantamentos. + A coluna 'origem_dados' indica a fonte original de cada registro. + columns: + - name: adiantamento_salario_ferias + description: > + Indica se houve adiantamento de salário referente ao período de férias. + - name: ano_exercicio + description: > + Ano de exercício a que o afastamento ou as férias se referem. + - name: dt_fim + description: > + Data de término do período de afastamento ou férias. + - name: dt_fim_aquisicao + description: > + Data final do período aquisitivo de férias. + - name: dt_ini + description: > + Data de início do período de afastamento ou férias. + - name: dt_inicio_aquisicao + description: > + Data inicial do período aquisitivo de férias. + - name: dt_inicio_ferias_interrompidas + description: > + Data de início de um período de férias que foi posteriormente interrompido. + - name: dias_restantes + description: > + Quantidade de dias restantes de um período de férias ou afastamento, usualmente após uma interrupção. + - name: gratificacao_natalina + description: > + Indicador se o afastamento está relacionado ao gozo ou adiantamento da gratificação natalina (13º salário). + - name: numero_parcela + description: > + Número da parcela, caso o afastamento ou férias seja dividido em múltiplos períodos. + - name: parcela_continuacao_interrupcao + description: > + Indicador se a parcela é uma continuação de um período anterior ou uma interrupção. + - name: parcelainterrompida + description: > + Indicador se a parcela de férias em questão foi interrompida. + - name: qtde_dias + description: > + Quantidade total de dias do afastamento ou do período de férias. + - name: cpf + description: > + CPF do servidor ao qual o registro de afastamento se refere. + - name: cod_diploma_afastamento + description: > + Código do diploma legal (lei, decreto, portaria) que fundamenta o afastamento. + - name: cod_ocorrencia + description: > + Código numérico ou alfanumérico que identifica o tipo de ocorrência (ex: tipo de licença, férias). + - name: dt_publicacao_afastamento + description: > + Data em que o ato de concessão do afastamento foi publicado oficialmente. + - name: desc_diploma_afastamento + description: > + Descrição textual do diploma legal que fundamenta o afastamento. + - name: desc_ocorrencia + description: > + Descrição textual do tipo de ocorrência (ex: 'FÉRIAS REGULAMENTARES', 'LICENÇA MÉDICA'). + - name: numero_diploma_afastamento + description: > + Número do diploma legal (lei, decreto, portaria) que fundamenta o afastamento. + - name: gr_matricula + description: > + Matrícula GR (Guia de Recolhimento) associada ao afastamento. Pode ser nulo para registros históricos. + - name: origem_dados + description: > + Indica a tabela de origem do registro, sendo 'dados_afastamento' para registros mais recentes + e 'afastamento_historico' para registros mais antigos. + ## Golds - name: contratos_resumo description: > @@ -1305,6 +1716,818 @@ models: Nome descritivo do tipo de vínculo do alimentado com o servidor (ex: FILHO(A), EX-CONJUGE). data_type: varchar + - name: dados_servidores + description: > + Tabela bronze contendo dados funcionais e de vínculo de servidores, + após limpeza básica e padronização de formatos a partir da fonte bruta. + columns: + - name: cod_atividade_funcao + description: > + Código da atividade da função. + data_type: varchar + - name: cod_funcao + description: > + Código da função exercida. + data_type: varchar + - name: cod_jornada + description: > + Código da jornada de trabalho. + data_type: varchar + - name: cod_ocorr_ingresso_orgao + description: > + Código da ocorrência de ingresso no órgão. + data_type: varchar + - name: cod_ocorr_ingresso_serv_publico + description: > + Código da ocorrência de ingresso no serviço público. + data_type: varchar + - name: cod_orgao + description: > + Código do órgão. + data_type: varchar + - name: cod_padrao + description: > + Código do padrão de vencimento. + data_type: varchar + - name: cod_situacao_funcional + description: > + Código da situação funcional. + data_type: varchar + - name: cod_uorg_exercicio + description: > + Código da Unidade Organizacional de exercício. + data_type: varchar + - name: cod_upag + description: > + Código da Unidade Pagadora (UPAG). + data_type: varchar + - name: cod_orgao_origem + description: > + Código do órgão de origem do servidor. + data_type: varchar + - name: cpf_chefia_imediata + description: > + CPF da chefia imediata, limpo (apenas dígitos). + data_type: varchar(11) + - name: dt_exercicio_no_orgao + description: > + Data de início do exercício no órgão. + data_type: date + - name: dt_fim_vale_ar + description: > + Data final de validade do vale AR (Alimentação/Refeição). + data_type: date + - name: dt_ingresso_funcao + description: > + Data de ingresso na função atual. + data_type: date + - name: dt_ocorr_ingresso_orgao + description: > + Data da ocorrência de ingresso no órgão. + data_type: date + - name: dt_ocorr_ingresso_serv_publico + description: > + Data da ocorrência de ingresso no serviço público. + data_type: date + - name: email_chefia_imediata + description: > + E-mail da chefia imediata. + data_type: varchar + - name: email_institucional + description: > + E-mail institucional do servidor. + data_type: varchar + - name: email_servidor + description: > + E-mail pessoal do servidor. + data_type: varchar + - name: ident_unica + description: > + Identificador único do servidor. + data_type: varchar + - name: matricula_siape + description: > + Matrícula SIAPE do servidor. + data_type: varchar + # tests: + # - not_null + # - unique + - name: modalidade_pgd + description: > + Modalidade do Programa de Gestão de Desempenho (PGD). + data_type: varchar + - name: nome_atividade_funcao + description: > + Nome da atividade da função. + data_type: varchar + - name: nome_chefe_uorg + description: > + Nome da chefia da UORG. + data_type: varchar + - name: nome_funcao + description: > + Nome da função exercida. + data_type: varchar + - name: nome_jornada + description: > + Nome descritivo da jornada de trabalho. + data_type: varchar + - name: nome_ocorr_ingresso_orgao + description: > + Nome descritivo da ocorrência de ingresso no órgão. + data_type: varchar + - name: nome_ocorr_ingresso_serv_publico + description: > + Nome descritivo da ocorrência de ingresso no serviço público. + data_type: varchar + - name: nome_orgao + description: > + Nome do órgão. + data_type: varchar + - name: nome_regime_juridico + description: > + Nome do regime jurídico do servidor. + data_type: varchar + - name: nome_situacao_funcional + description: > + Nome descritivo da situação funcional (ex: ATIVO PERMANENTE, APOSENTADO). + data_type: varchar + - name: nome_uorg_exercicio + description: > + Nome da Unidade Organizacional de exercício. + data_type: varchar + - name: nome_upag + description: > + Nome da Unidade Pagadora (UPAG). + data_type: varchar + - name: participa_pgd + description: > + Indica se o servidor participa do PGD (ex: sim, não). + data_type: varchar + - name: percentual_ts + description: > + Percentual de Tempo de Serviço (TS), como valor numérico (ex: 12% = 0.12) + data_type: numeric + - name: sigla_orgao + description: > + Sigla do órgão. + data_type: varchar + - name: sigla_orgao_origem + description: > + Sigla do órgão de origem. + data_type: varchar + - name: sigla_regime_juridico + description: > + Sigla do regime jurídico. + data_type: varchar + - name: sigla_uorg_exercicio + description: > + Sigla da UORG de exercício. + data_type: varchar + - name: sigla_upag + description: > + Sigla da UPAG. + data_type: varchar + - name: cpf + description: > + CPF do servidor, contendo apenas os 11 dígitos. + data_type: varchar(11) + # tests: + # - not_null + # - dbt_utils.equal_length: + # value: 11 + - name: cod_cargo + description: > + Código do cargo do servidor. + data_type: varchar + - name: cod_classe + description: > + Código da classe do cargo. + data_type: varchar + - name: cod_ocorr_aposentadoria + description: > + Código da ocorrência de aposentadoria. + data_type: varchar + - name: dt_ini_vale_ar + description: > + Data inicial de validade do vale AR. + data_type: date + - name: dt_ocorr_aposentadoria + description: > + Data da ocorrência de aposentadoria. + data_type: date + - name: nome_cargo + description: > + Nome do cargo do servidor. + data_type: varchar + - name: nome_classe + description: > + Nome da classe do cargo. + data_type: varchar + - name: nome_ocorr_aposentadoria + description: > + Nome descritivo da ocorrência de aposentadoria. + data_type: varchar + - name: sigla_nivel_cargo + description: > + Sigla do nível do cargo (ex: NS, NI). + data_type: varchar + - name: tipo_vale_ar + description: > + Tipo do vale AR (ex: ALIMENTACAO, REFEICAO). + data_type: varchar + - name: cod_ocorr_isencao_ir + description: > + Código da ocorrência de isenção de Imposto de Renda. + data_type: varchar + - name: dt_ini_ocorr_isencao_ir + description: > + Data inicial da ocorrência de isenção de IR. + data_type: date + - name: nome_ocorr_isencao_ir + description: > + Nome descritivo da ocorrência de isenção de IR. + data_type: varchar + - name: cod_uorg_lotacao + description: > + Código da Unidade Organizacional de lotação. + data_type: varchar + - name: nome_uorg_lotacao + description: > + Nome da Unidade Organizacional de lotação. + data_type: varchar + - name: sigla_uorg_lotacao + description: > + Sigla da UORG de lotação. + data_type: varchar + - name: dt_fim_ocorr_isencao_ir + description: > + Data final da ocorrência de isenção de IR. + data_type: date + - name: cod_ocorr_exclusao + description: > + Código da ocorrência de exclusão. + data_type: varchar + - name: dt_ocorr_exclusao + description: > + Data da ocorrência de exclusão. + data_type: date + - name: nome_ocorr_exclusao + description: > + Nome descritivo da ocorrência de exclusão. + data_type: varchar + - name: dt_uorg_lotacao + description: > + Data de lotação na UORG. + data_type: date + - name: cod_vale_transporte + description: > + Código do vale transporte. + data_type: varchar + - name: valor_vale_transporte + description: > + Valor do vale transporte (como string). + data_type: numeric + - name: dt_uorg_exercicio + description: > + Data de exercício na UORG. + data_type: date + - name: pontuacao_desempenho + description: > + Pontuação de desempenho (mantido como string). + data_type: varchar + + - name: dados_financeiros + description: > + Tabela bronze contendo dados de rubricas financeiras ou de pagamento, + após limpeza básica e padronização de formatos + columns: + - name: cod_rubrica + description: > + Código da rubrica. + data_type: varchar + # tests: + # - not_null + + - name: indicador_rd + description: > + Indicador de Rendimento/Desconto (ex: D). + data_type: varchar + + - name: nome_rubrica + description: > + Nome descritivo da rubrica. + data_type: varchar + + - name: numero_sequencia + description: > + Número sequencial associado à rubrica. + data_type: varchar + + - name: valor_rubrica + description: > + Valor monetário associado à rubrica, convertido para NUMERIC. + data_type: numeric + # tests: + # - not_null + + - name: data_anomes_rubrica + description: > + Mês e ano da rubrica, convertido para DATE (representa o 1º dia do mês). + data_type: date + + - name: prazo_rubrica + description: > + Prazo associado à rubrica (ex: 001). + data_type: varchar + + - name: mes_ano_pagamento + description: > + Mês e ano do pagamento, convertido para DATE (representa o 1º dia do mês). + data_type: date + + - name: cpf + description: > + CPF do indivíduo associado, contendo apenas os 11 dígitos. + data_type: varchar(11) + # tests: + # - not_null + # - dbt_utils.equal_length: + # value: 11 + + - name: indicador_mov_supl + description: > + Indicador de movimento suplementar. + data_type: varchar + + - name: periodo_rubrica + description: > + Período da rubrica. + data_type: varchar + + - name: dados_escolares + description: > + Tabela bronze contendo dados acadêmicos e de formação de indivíduos, + após limpeza básica e padronização de formatos a partir da fonte bruta. + columns: + - name: cod_curso + description: > + Código do curso. + data_type: varchar + + - name: nome_curso + description: > + Nome do curso. + data_type: varchar + + - name: cod_matricula + description: > + Código da matrícula associada ao curso ou indivíduo. + data_type: varchar + # tests: + # - not_null # Se for uma chave + + - name: cod_orgao + description: > + Código do órgão (possivelmente da instituição de ensino ou do servidor). + data_type: varchar + + - name: cod_titulacao + description: > + Código da titulação obtida (ex: Doutorado, Graduação). + data_type: varchar + + - name: nome_titulacao + description: > + Nome descritivo da titulação. + data_type: varchar + + - name: cod_escolaridade + description: > + Código do nível de escolaridade. + data_type: varchar + + - name: nome_escolaridade + description: > + Nome descritivo do nível de escolaridade. + data_type: varchar + + - name: cpf + description: > + CPF do indivíduo, contendo apenas os 11 dígitos. + data_type: varchar(11) + # tests: + # - not_null + # - dbt_utils.equal_length: + # value: 11 +models: + - name: dados_dependentes + description: > + Tabela bronze contendo dados de dependentes e benefícios associados, + após limpeza básica e padronização de formatos a partir da fonte bruta. + + columns: + - name: cod_condicao + description: > + Código da condição do dependente. + data_type: varchar + + - name: cod_grau_parentesco + description: > + Código do grau de parentesco do dependente. + data_type: varchar + + - name: cod_orgao + description: > + Código do órgão do servidor principal. + data_type: varchar + + - name: cpf + description: > + CPF do dependente, contendo apenas os 11 dígitos (se aplicável). + data_type: varchar(11) + # tests: + # - dbt_utils.equal_length: + # value: 11 + # config: + # severity: warn + + - name: matricula + description: > + Matrícula do servidor principal. + data_type: varchar + # tests: + # - not_null + + - name: nome_dependente + description: > + Nome completo do dependente. + data_type: varchar + # tests: + # - not_null + + - name: nome_condicao + description: > + Nome descritivo da condição do dependente. + data_type: varchar + + - name: nome_grau_parentesco + description: > + Nome descritivo do grau de parentesco do dependente. + data_type: varchar + + - name: cod_beneficio + description: > + Código do benefício associado ao dependente. + data_type: varchar + + - name: dt_fim + description: > + Data final do benefício ou da condição, convertida para DATE. + data_type: date + + - name: dt_inicio + description: > + Data inicial do benefício ou da condição, convertida para DATE. + data_type: date + + - name: nome_beneficio + description: > + Nome descritivo do benefício. + data_type: varchar + + - name: dados_curriculo + description: > + Tabela bronze contendo dados de experiência + após limpeza básica e padronização de formatos a partir da fonte bruta. + + columns: + - name: cpf + description: > + CPF do indivíduo, contendo apenas os 11 dígitos. + data_type: varchar(11) + # tests: + # - not_null + + - name: ident_unica + description: > + Identificador único associado. + data_type: varchar + + - name: codigo_experiencia + description: > + Código geral da experiência/qualificação. + data_type: varchar + + - name: cod_curso + description: > + Código do curso, se aplicável. + data_type: varchar + + - name: nome_curso + description: > + Nome do curso, se aplicável. + data_type: varchar + + - name: dt_mes_conclusao + description: > + Data de conclusão (formato YYYYMM), convertida para DATE (1º dia do mês). + data_type: date + + - name: nome_instituicao + description: > + Nome da instituição de ensino ou empresa. + data_type: varchar + + - name: nome_area_experiencia + description: > + Nome da área ou tipo de experiência (ex: Licitação, Auditoria). + data_type: varchar + + - name: carga_horaria + description: > + Carga horária associada. + data_type: varchar # Mantido como varchar, pode ser convertido para integer/numeric no silver + + - name: nome_cargo + description: > + Nome do cargo ocupado. + data_type: varchar + + - name: dt_mes_inicio + description: > + Data de início da experiência (formato YYYYMM), convertida para DATE (1º dia do mês). + data_type: date + + - name: nome_orgao_empresa + description: > + Nome do órgão ou empresa onde a experiência ocorreu. + data_type: varchar + + - name: dt_mes_fim + description: > + Data de fim da experiência (formato YYYYMM), convertida para DATE (1º dia do mês). + data_type: date + + - name: descricao_projeto + description: > + Descrição detalhada do projeto ou atividades realizadas. + data_type: text # Usar 'text' para campos potencialmente longos ou multi-linha + + - name: informacoes_adicionais + description: > + Informações adicionais sobre a experiência. + data_type: text # Usar 'text' para campos potencialmente longos ou multi-linha + + - name: tipo_descricao + description: > + Tipo de descrição ou categoria adicional. + data_type: varchar + + - name: dados_afastamento + description: > + Tabela bronze contendo dados de férias e afastamentos de servidores, + após limpeza básica e padronização de formatos a partir da fonte bruta. + Valores de data inválidos ou malformados na origem são convertidos para NULL. + + columns: + - name: adiantamento_salario_ferias + description: > + Indicador de adiantamento de salário nas férias (ex: N, S). + data_type: varchar + + - name: ano_exercicio + description: > + Ano de exercício das férias ou afastamento. + data_type: varchar # ou integer se sempre for numérico e sem zeros à esquerda significativos + + - name: dt_fim + description: > + Data de fim das férias ou afastamento. + Convertida para DATE; strings de origem inválidas (ex: "0", comprimento incorreto) resultam em NULL. + data_type: date + + - name: dt_fim_aquisicao + description: > + Data de fim do período aquisitivo. + Convertida para DATE; strings de origem inválidas resultam em NULL. + data_type: date + + - name: dt_ini + description: > + Data de início das férias ou afastamento. + Convertida para DATE; strings de origem inválidas resultam em NULL. + data_type: date + + - name: dt_inicio_aquisicao + description: > + Data de início do período aquisitivo. + Convertida para DATE; strings de origem inválidas resultam em NULL. + data_type: date + + - name: gratificacao_natalina + description: > + Indicador de gratificação natalina (13º salário) (ex: N, S). + data_type: varchar + + - name: numero_parcela + description: > + Número da parcela das férias. + data_type: integer + + - name: parcela_continuacao_interrupcao + description: > + Indicador se a parcela é continuação ou interrupção. + data_type: varchar + + - name: parcela_interrompida + description: > + Indicador se a parcela foi interrompida. + data_type: varchar + + - name: qtde_dias + description: > + Quantidade de dias de férias/afastamento. + data_type: integer + + - name: gr_matricula + description: > + Matrícula do servidor (possivelmente com código GR ou outro prefixo). + data_type: varchar + + - name: cpf + description: > + CPF do servidor, contendo apenas os 11 dígitos. + data_type: varchar(11) + # tests: + # - not_null + # - dbt_utils.equal_length: + # value: 11 + + - name: cod_diploma_afastamento + description: > + Código do diploma legal do afastamento. + data_type: varchar + + - name: cod_ocorrencia + description: > + Código da ocorrência (férias/afastamento). + data_type: varchar + + - name: dt_publicacao_afastamento + description: > + Data de publicação do ato de afastamento. + Convertida para DATE (formato de origem YYYYMMDD); strings de origem inválidas resultam em NULL. + data_type: date + + - name: desc_diploma_afastamento + description: > + Descrição do diploma legal do afastamento (ex: PORTARIA). + data_type: varchar + + - name: desc_ocorrencia + description: > + Descrição da ocorrência de afastamento. + data_type: varchar + + - name: numero_diploma_afastamento + description: > + Número do diploma legal do afastamento. + data_type: integer + + - name: dt_inicio_ferias_interrompidas + description: > + Data de início das férias que foram interrompidas. + Convertida para DATE; strings de origem inválidas resultam em NULL. + data_type: date + + - name: dias_restantes + description: > + Quantidade de dias restantes de férias após interrupção. + data_type: integer + + - name: afastamento_historico + description: > + Tabela bronze contendo dados históricos de férias e afastamentos de servidores, (semelhante a dados_afastamento) + após limpeza básica e padronização de formatos a partir da fonte bruta. + Valores de data inválidos ou malformados na origem são convertidos para NULL. + + columns: + - name: adiantamento_salario_ferias + description: > + Indicador de adiantamento de salário nas férias (ex: N, S). + data_type: varchar + + - name: gr_matricula + description: > + Matrícula do servidor (Não está presenta aqui, ficou como placeholder tudo null). + data_type: varchar + + - name: ano_exercicio + description: > + Ano de exercício das férias ou afastamento. + data_type: varchar + + - name: dt_fim + description: > + Data de fim das férias ou afastamento. + Convertida para DATE; strings de origem inválidas resultam em NULL. + data_type: date + + - name: dt_fim_aquisicao + description: > + Data de fim do período aquisitivo. + Convertida para DATE; strings de origem inválidas resultam em NULL. + data_type: date + + - name: dt_ini + description: > + Data de início das férias ou afastamento. + Convertida para DATE; strings de origem inválidas resultam em NULL. + data_type: date + + - name: dt_inicio_aquisicao + description: > + Data de início do período aquisitivo. + Convertida para DATE; strings de origem inválidas resultam em NULL. + data_type: date + + - name: dt_inicio_ferias_interrompidas + description: > + Data de início das férias que foram interrompidas. + Convertida para DATE; strings de origem inválidas resultam em NULL. + data_type: date + + - name: dias_restantes + description: > + Quantidade de dias restantes de férias após interrupção. + data_type: integer + + - name: gratificacao_natalina + description: > + Indicador de gratificação natalina (13º salário) (ex: N, S). + data_type: varchar + + - name: numero_parcela + description: > + Número da parcela das férias. + data_type: integer + + - name: parcela_continuacao_interrupcao + description: > + Indicador se a parcela é continuação ou interrupção. + data_type: varchar + + - name: parcela_interrompida + description: > + Indicador se a parcela foi interrompida. + data_type: varchar + + - name: qtde_dias + description: > + Quantidade de dias de férias/afastamento. + data_type: integer + + - name: cpf + description: > + CPF do servidor, contendo apenas os 11 dígitos. + data_type: varchar(11) + # tests: + # - not_null + # - dbt_utils.equal_length: + # value: 11 + + - name: cod_diploma_afastamento + description: > + Código do diploma legal do afastamento. + data_type: varchar + + - name: cod_ocorrencia + description: > + Código da ocorrência (férias/afastamento). + data_type: varchar + + - name: dt_publicacao_afastamento + description: > + Data de publicação do ato de afastamento. + Convertida para DATE (formato de origem YYYYMMDD); strings de origem inválidas resultam em NULL. + data_type: date + + - name: desc_diploma_afastamento + description: > + Descrição do diploma legal do afastamento (ex: PORTARIA). + data_type: varchar + + - name: desc_ocorrencia + description: > + Descrição da ocorrência de afastamento. + data_type: varchar + + - name: numero_diploma_afastamento + description: > + Número do diploma legal do afastamento. + data_type: integer + # Pessoas DBT # TED DBT diff --git a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_uorg.sql b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_uorg.sql deleted file mode 100644 index 86a6fa32..00000000 --- a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/dados_uorg.sql +++ /dev/null @@ -1,44 +0,0 @@ -with - dados_uorg as ( - select - bairrouorg, - cepuorg, - codmatricula, - codmunicipiouorg, - codorgao, - codorgaouorg, - emailuorg, - enduorg, - logradourouorg, - nomemunicipiouorg, - nomeuorg, - numtelefoneuorg, - numerouorg, - siglauorg, - ufuorg, - cpf, - complementouorg, - numfaxuorg - from {{ source("siape", "dados_uorg") }} - ) - -select - nullif(trim(bairrouorg), '') as bairro_uorg, - regexp_replace(nullif(trim(cepuorg), ''), '[^0-9]', '', 'g') as cep_uorg, - nullif(trim(codmatricula), '') as codigo_matricula, - nullif(trim(codmunicipiouorg), '') as codigo_municipio_uorg, - nullif(trim(codorgao), '') as codigo_orgao, - nullif(trim(codorgaouorg), '') as codigo_orgao_u - lower(nullif(trim(emailuorg), '')) as email_uorg, - nullif(trim(enduorg), '') as tipo_endereco_uorg, - nullif(trim(logradourouorg), '') as logradouro_uorg, - nullif(trim(nomemunicipiouorg), '') as nome_municipio_uorg, - nullif(trim(nomeuorg), '') as nome_uorg, - regexp_replace(nullif(trim(numtelefoneuorg), ''), '[^0-9]', '', 'g') as telefone_uorg, - nullif(trim(numerouorg), '') as numero_endereco_uorg, - nullif(trim(siglauorg), '') as sigla_uorg, - upper(nullif(trim(ufuorg), '')) as uf_uorg, - regexp_replace(nullif(trim(cpf), ''), '[^0-9]', '', 'g') as cpf, - nullif(nullif(trim(complementouorg), ''), '---') as complemento_endereco_uorg, - regexp_replace(nullif(trim(numfaxuorg), ''), '[^0-9]', '', 'g') as fax_uorg -from dados_uorg diff --git a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/lista_uorgs.sql b/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/lista_uorgs.sql deleted file mode 100644 index 6e2d832e..00000000 --- a/airflow_lappis/dags/dbt/ipea/models/siape_dbt/bronze/lista_uorgs.sql +++ /dev/null @@ -1,11 +0,0 @@ -with - lista_uorgs as ( - select - cast(codigo as int) as codigo, - to_date(dataultimatransacao, 'DDMMYYYY') as dt_ultima_transacao, - nome - from {{ source("siape", "lista_uorgs") }} - ) - -select * -from lista_uorgs diff --git a/airflow_lappis/dags/dbt/ipea/models/sources.yml b/airflow_lappis/dags/dbt/ipea/models/sources.yml index 61c9df4d..f0b5eb89 100644 --- a/airflow_lappis/dags/dbt/ipea/models/sources.yml +++ b/airflow_lappis/dags/dbt/ipea/models/sources.yml @@ -32,6 +32,13 @@ sources: - name: dados_uorg - name: dados_pessoais - name: dados_pa + - name: dados_funcionais + - name: dados_financeiros + - name: dados_escolares + - name: dados_dependentes + - name: dados_curriculo + - name: dados_afastamento + - name: afastamento_historico - name: siorg schema: siorg From ec96039156812e9adfd8fcabb97949e1e3e22e12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Freitas?= Date: Wed, 4 Jun 2025 04:00:09 +0000 Subject: [PATCH 124/317] fix pessoas models --- .../models/pessoas_dbt/bronze/dados_pa.sql | 29 ---------- .../models/pessoas_dbt/bronze/dados_uorg.sql | 55 +------------------ .../pessoas_dbt/bronze/lista_servidores.sql | 13 ----- .../models/pessoas_dbt/bronze/lista_uorgs.sql | 15 +---- 4 files changed, 2 insertions(+), 110 deletions(-) diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_pa.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_pa.sql index 32676ccc..c6fc3872 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_pa.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_pa.sql @@ -1,26 +1,3 @@ -with - dados_pa as ( - select - agenciabeneficiario, - bancobeneficiario, - codorgao, - contabeneficiario, - cpfbeneficiario, - matricula, - nomebeneficiario, - valorultimapensao, - cpf, - codvinculoservidor, - nomealimentado, - nomevinculoservidor - from {{ source("siape", "dados_pa") }} - ) - - << << - << < head - == == - == = - with dados_pa as ( select @@ -39,8 +16,6 @@ with from {{ source("siape", "dados_pa") }} ) - >> >> - >> > 2884731 (changed tenant siape dbt to pessoas dbt) select regexp_replace( nullif(trim(agenciabeneficiario), ''), '[^0-9]', '', 'g' @@ -56,11 +31,7 @@ select nullif(trim(matricula), '') as matricula_servidor, nullif(trim(nomebeneficiario), '') as nome_beneficiario, nullif(trim(valorultimapensao), '') as valor_ultima_pensao, - << << << < head - regexp_replace(nullif(trim(cpf), ''), '[^0-9]', '', 'g') as cpf_servidor, - == == == = regexp_replace(nullif(trim(cpf_servidor), ''), '[^0-9]', '', 'g') as cpf_servidor, - >> >> >> > 2884731 (changed tenant siape dbt to pessoas dbt) nullif(trim(codvinculoservidor), '') as cod_vinculo_servidor, nullif(trim(nomealimentado), '') as nome_alimentado, nullif(trim(nomevinculoservidor), '') as nome_vinculo_servidor diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_uorg.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_uorg.sql index a0edb35b..29d86cfb 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_uorg.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_uorg.sql @@ -1,4 +1,3 @@ -<< << << < head with dados_uorg as ( select @@ -22,59 +21,7 @@ with numfaxuorg from {{ source("siape", "dados_uorg") }} ) - == == - == = -with - dados_uorg as ( - select - bairrouorg, - cepuorg, - codmatricula, - codmunicipiouorg, - codorgao, - codorgaouorg, - emailuorg, - enduorg, - logradourouorg, - nomemunicipiouorg, - nomeuorg, - numtelefoneuorg, - numerouorg, - siglauorg, - ufuorg, - cpf, - complementouorg, - numfaxuorg - from {{ source("siape", "dados_uorg") }} - ) - >> >> - >> > b2eef63( - created afastamento_consolidado - and servidores_detalhados tables at siape silver tenant - ) - << << - << < head -select - nullif(trim(bairrouorg), '') as bairro_uorg, - regexp_replace(nullif(trim(cepuorg), ''), '[^0-9]', '', 'g') as cep_uorg, - nullif(trim(codmatricula), '') as codigo_matricula, - nullif(trim(codmunicipiouorg), '') as codigo_municipio_uorg, - nullif(trim(codorgao), '') as codigo_orgao, - nullif(trim(codorgaouorg), '') as codigo_orgao_u - lower(nullif(trim(emailuorg), '')) as email_uorg, - nullif(trim(enduorg), '') as tipo_endereco_uorg, - nullif(trim(logradourouorg), '') as logradouro_uorg, - nullif(trim(nomemunicipiouorg), '') as nome_municipio_uorg, - nullif(trim(nomeuorg), '') as nome_uorg, - regexp_replace(nullif(trim(numtelefoneuorg), ''), '[^0-9]', '', 'g') as telefone_uorg, - nullif(trim(numerouorg), '') as numero_endereco_uorg, - nullif(trim(siglauorg), '') as sigla_uorg, - upper(nullif(trim(ufuorg), '')) as uf_uorg, - regexp_replace(nullif(trim(cpf), ''), '[^0-9]', '', 'g') as cpf, - nullif(nullif(trim(complementouorg), ''), '---') as complemento_endereco_uorg, - regexp_replace(nullif(trim(numfaxuorg), ''), '[^0-9]', '', 'g') as fax_uorg -from dados_uorg == == == = select nullif(trim(bairrouorg), '') as bairro_uorg, regexp_replace(nullif(trim(cepuorg), ''), '[^0-9]', '', 'g') as cep_uorg, @@ -94,4 +41,4 @@ select regexp_replace(nullif(trim(cpf), ''), '[^0-9]', '', 'g') as cpf, nullif(nullif(trim(complementouorg), ''), '---') as complemento_endereco_uorg, regexp_replace(nullif(trim(numfaxuorg), ''), '[^0-9]', '', 'g') as fax_uorg -from dados_uorg >> >> >> > 2884731 (changed tenant siape dbt to pessoas dbt) +from dados_uorg diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_servidores.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_servidores.sql index 788db43c..5b9c7873 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_servidores.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_servidores.sql @@ -1,21 +1,8 @@ -<< << << < head -with - lista_servidores as ( - select - cpf, - to_date(dataultimatransacao, 'DDMMYYYY') as dt_ultima_transacao, - coduorg as cod_uorg - from {{ source("siape", "lista_servidores") }} - ) - == == - == = with lista_servidores as ( select cpf, dt_ultima_transacao, cod_uorg from {{ source("siape", "lista_servidores") }} ) - >> >> - >> > 2884731 (changed tenant siape dbt to pessoas dbt) select * from lista_servidores diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_uorgs.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_uorgs.sql index c43af9c3..1aea4a0a 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_uorgs.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_uorgs.sql @@ -1,16 +1,3 @@ -with - lista_uorgs as ( - select - cast(codigo as int) as codigo, - to_date(dataultimatransacao, 'DDMMYYYY') as dt_ultima_transacao, - nome - from {{ source("siape", "lista_uorgs") }} - ) - - << << - << < head -select * -from lista_uorgs == == == = with lista_uorgs as ( select cast(codigo as int) as codigo, dt_ultima_transacao, nome @@ -18,4 +5,4 @@ with ) select * -from lista_uorgs >> >> >> > 2884731 (changed tenant siape dbt to pessoas dbt) +from lista_uorgs From 52f504c58ee061840aef1fdee4c495b9006affa7 Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Fri, 6 Jun 2025 15:36:13 +0000 Subject: [PATCH 125/317] Fix/ingestao siorg --- ...rutura_organizacional_cargos_ingest_dag.py | 7 +++- .../estrutura_organizacional_cargos.sql | 39 ++++++++++++++++--- .../bronze/unidade_organizacional.sql | 19 +++++++-- airflow_lappis/plugins/cliente_postgres.py | 19 +++++---- 4 files changed, 67 insertions(+), 17 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/estrutura_organizacional_cargos_ingest_dag.py b/airflow_lappis/dags/data_ingest/estrutura_organizacional_cargos_ingest_dag.py index 4de00bd2..f53309a9 100644 --- a/airflow_lappis/dags/data_ingest/estrutura_organizacional_cargos_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/estrutura_organizacional_cargos_ingest_dag.py @@ -27,13 +27,18 @@ def fetch_estrutura_organizacional_cargos() -> None: logging.warning("Nenhum código de unidade encontrado.") return - for codigo_unidade in codigo_unidades: + for unidade in codigo_unidades: + codigo_unidade = unidade["codigounidade"] + ordem_grandeza = unidade["ordem_grandeza"] + try: estrutura_cargos = api.get_estrutura_organizacional_cargos( codigo_unidade ) if estrutura_cargos: + estrutura_cargos["ordem_grandeza"] = ordem_grandeza + db.insert_data( [estrutura_cargos], "estrutura_organizacional_cargos", diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/estrutura_organizacional_cargos.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/estrutura_organizacional_cargos.sql index 227d456c..28a95d10 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/estrutura_organizacional_cargos.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/estrutura_organizacional_cargos.sql @@ -1,12 +1,25 @@ with fonte as ( - select codigounidade, nomeunidade, siglaunidade, municipio, uf, cargos + select + codigounidade, + nomeunidade, + siglaunidade, + municipio, + uf, + cargos, + ordem_grandeza from {{ source("siorg", "estrutura_organizacional_cargos") }} ), cargos_expandidos as ( select - f.codigounidade, f.nomeunidade, f.siglaunidade, f.municipio, f.uf, cargo_elem + f.codigounidade, + f.nomeunidade, + f.siglaunidade, + f.municipio, + f.uf, + f.ordem_grandeza, + cargo_elem from fonte f, lateral jsonb_array_elements( @@ -21,6 +34,7 @@ with ce.siglaunidade, ce.municipio, ce.uf, + ce.ordem_grandeza, cargo_elem ->> 'denominacao' as denominacao, cargo_elem ->> 'funcao' as funcao, instancia_elem ->> 'codigoInstancia' as codigo_instancia, @@ -29,6 +43,20 @@ with from cargos_expandidos ce, lateral jsonb_array_elements(cargo_elem -> 'instancias') as instancia_elem + ), + + instancias_filtradas as ( + select * + from + ( + select + *, + row_number() over ( + partition by codigo_instancia order by ordem_grandeza desc + ) as rn + from instancias_expandidas + ) t + where rn = 1 ) select @@ -39,7 +67,8 @@ select uf, denominacao, funcao, - codigo_instancia::bigint as codigo_instancia, + codigo_instancia, nome_titular, - cpf_titular -from instancias_expandidas + cpf_titular, + ordem_grandeza +from instancias_filtradas diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/unidade_organizacional.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/unidade_organizacional.sql index cebb963d..b97a3b51 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/unidade_organizacional.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/unidade_organizacional.sql @@ -1,4 +1,4 @@ -with +with recursive fonte as ( select regexp_replace(codigounidade, '^.*/', '') as codigounidade, @@ -13,13 +13,26 @@ with codigosubnaturezajuridica, nivelnormatizacao, versaoconsulta, - datainicialversaoconsulta, datafinalversaoconsulta, operacao, codigounidadepaianterior, codigoorgaoentidadeanterior from {{ source("siorg", "unidade_organizacional") }} + ), + + unidades_raiz as (select '7' as codigounidade_raiz), + + hierarquia as ( + select f.*, 1 as ordem_grandeza + from fonte f + join unidades_raiz r on f.codigounidade = r.codigounidade_raiz + + union all + + select f.*, h.ordem_grandeza + 1 as ordem_grandeza + from fonte f + join hierarquia h on f.codigounidadepai = h.codigounidade ) select * -from fonte +from hierarquia diff --git a/airflow_lappis/plugins/cliente_postgres.py b/airflow_lappis/plugins/cliente_postgres.py index 293e4824..37e5cc9f 100755 --- a/airflow_lappis/plugins/cliente_postgres.py +++ b/airflow_lappis/plugins/cliente_postgres.py @@ -378,18 +378,21 @@ def remove_duplicates( ) raise - def get_codigo_unidade(self) -> list[int]: - """Retorna o código da unidade da tabela unidade_organizacional.""" - query = ( - "SELECT regexp_replace(codigounidade, '.*/', '') " - "FROM siorg.unidade_organizacional" - ) + def get_codigo_unidade(self) -> list[dict]: + """Retorna código da unidade e ordem de grandeza da tabela.""" + query = """ + SELECT codigounidade, ordem_grandeza + FROM pessoas.unidade_organizacional + """ with psycopg2.connect(self.conn_str) as conn: with conn.cursor() as cursor: cursor.execute(query) - codigo_unidade = [int(row[0]) for row in cursor.fetchall()] - return codigo_unidade + rows = cursor.fetchall() + return [ + {"codigounidade": int(row[0]), "ordem_grandeza": int(row[1])} + for row in rows + ] def execute_non_query(self, query: str) -> None: """ From 91b3e84b4f7d4e996778dcbd50884df52e6f9da8 Mon Sep 17 00:00:00 2001 From: arthrok Date: Mon, 9 Jun 2025 23:02:28 -0300 Subject: [PATCH 126/317] feat(ci): adiciona deploy dbt-docs --- .gitlab-ci.yml | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2f14c954..3aebcb73 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,7 +8,7 @@ variables: POETRY_VIRTUALENVS_IN_PROJECT: "true" POETRY_CACHE_DIR: "/tmp/poetry-cache" PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" - DBT_PROJECT_DIR: "${CI_PROJECT_DIR}/plugins/dbt" + DBT_PROJECT_DIR: "${CI_PROJECT_DIR}/airflow_lappis/dags/dbt/ipea" GIT_CI_USER: "ci bot" GIT_CI_EMAIL: "ci.lappis.rocks@gmail.com" DOCKER_TLS_CERTDIR: "/certs" @@ -16,7 +16,7 @@ variables: cache: paths: - # - .cache/poetry + - .cache/poetry - .cache/pip - .venv - .cache/docker @@ -25,6 +25,7 @@ stages: - lint - test - build + - deploy .install-poetry: before_script: @@ -70,6 +71,46 @@ scan: - src/** - tests/** +pages: + stage: deploy + image: python:3.11-slim + rules: + - if: $CI_COMMIT_BRANCH == "main" + before_script: + - apt-get update && apt-get install -y openvpn iputils-ping + - mkdir -p /etc/openvpn ci/dbt + - cat "$CLIENT_OVPN" >/etc/openvpn/client.ovpn + - cat "$VPN_P12" | base64 --decode > /etc/openvpn/openvpn_ipea_vpn.p12 + - chmod 600 /etc/openvpn/openvpn_ipea_vpn.p12 + - echo "$VPN_USER" >/etc/openvpn/cred.txt + - echo "$VPN_PWD" >>/etc/openvpn/cred.txt + - chmod 600 /etc/openvpn/cred.txt + - openvpn --config /etc/openvpn/client.ovpn --auth-user-pass /etc/openvpn/cred.txt --verb 3 --daemon --log /tmp/ovpn.log + - | + timeout=60 + until grep -q 'Initialization Sequence Completed' /tmp/ovpn.log; do + sleep 1 + (( timeout-- )) + if (( timeout == 0 )); then + echo "❌ VPN não inicializou em 60s" >&2 + cat /tmp/ovpn.log + exit 1 + fi + done + - ping -c 4 10.0.0.73 || true + - pip install dbt-core dbt-postgres + script: + - cd "$DBT_PROJECT_DIR" + - dbt deps + - dbt docs generate + - mkdir -p "${CI_PROJECT_DIR}/public" + - mv target/* "${CI_PROJECT_DIR}/public/" + - pkill openvpn || true + artifacts: + paths: + - public + expire_in: 1 week + docker-build-and-push: stage: build image: docker:20.10.16 @@ -86,3 +127,4 @@ docker-build-and-push: - Dockerfile - requirements.txt - pyproject.toml + \ No newline at end of file From 27e3e78b16c6b6a2012923ab3f2d5a0d7873950a Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Thu, 12 Jun 2025 14:48:56 +0000 Subject: [PATCH 127/317] feat(siape): dag de ingestao de pensoes instituidas --- .../pensoes_instituidas_siape_ingest_dag.py | 90 +++++++++++++++++++ airflow_lappis/plugins/cliente_siape.py | 31 +++++++ .../siape/consultaPensoesInstituidas.xml.j2 | 15 ++++ 3 files changed, 136 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/pensoes_instituidas_siape_ingest_dag.py create mode 100644 airflow_lappis/templates/siape/consultaPensoesInstituidas.xml.j2 diff --git a/airflow_lappis/dags/data_ingest/pensoes_instituidas_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/pensoes_instituidas_siape_ingest_dag.py new file mode 100644 index 00000000..2e49ceca --- /dev/null +++ b/airflow_lappis/dags/data_ingest/pensoes_instituidas_siape_ingest_dag.py @@ -0,0 +1,90 @@ +import os +import logging +from datetime import datetime, timedelta +from airflow.decorators import dag, task +from postgres_helpers import get_postgres_conn +from cliente_siape import ClienteSiape +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Davi", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["siape", "pensoes_instituidas"], +) +def siape_pensoes_instituidas_dag() -> None: + """ + DAG que consome o endpoint consultaPensoesInstituidas da API SIAPE + e armazena dados de pensões instituídas no schema 'siape'. + """ + + @task + def fetch_and_store_pensoes_instituidas() -> None: + logging.info("Iniciando extração de dados de pensões instituídas por CPF") + cliente_siape = ClienteSiape() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + query = "SELECT DISTINCT cpf FROM siape.lista_servidores WHERE cpf IS NOT NULL" + cpfs = [row[0] for row in db.execute_query(query)] + logging.info(f"Total de CPFs encontrados: {len(cpfs)}") + + for cpf in cpfs: + try: + context = { + "siglaSistema": "PETRVS-IPEA", + "nomeSistema": "PDG-PETRVS-IPEA", + "senha": os.getenv("SIAPE_PASSWORD_USER"), + "cpf": cpf, + "codOrgao": "45206", + "parmExistPag": "b", + "parmTipoVinculo": "c", + } + + resposta_xml = cliente_siape.call( + "consultaPensoesInstituidas.xml.j2", context + ) + dados = ClienteSiape.parse_pensoes_instituidas(resposta_xml) + + if not dados: + logging.warning(f"Nenhum dado de pensão instituída para CPF {cpf}") + continue + + # Adiciona CPF a cada registro + for registro in dados: + registro["cpf"] = cpf + + if dados: + # Usa o primeiro registro para criar/ajustar a estrutura da tabela + db.alter_table( + data=dados[0], + table_name="pensoes_instituidas", + schema="siape", + ) + + db.insert_data( + dados, + table_name="pensoes_instituidas", + conflict_fields=["cpf"], + primary_key=["cpf"], + schema="siape", + ) + + logging.info( + f"Inseridos {len(dados)} registros de pensões instituídas: {cpf}" + ) + + except Exception as e: + logging.error(f"Erro ao processar CPF {cpf}: {e}") + continue + + fetch_and_store_pensoes_instituidas() + + +dag_instance = siape_pensoes_instituidas_dag() diff --git a/airflow_lappis/plugins/cliente_siape.py b/airflow_lappis/plugins/cliente_siape.py index cf1abc55..87f3046c 100755 --- a/airflow_lappis/plugins/cliente_siape.py +++ b/airflow_lappis/plugins/cliente_siape.py @@ -261,3 +261,34 @@ def parse_dependentes(xml_string: str) -> list[dict[str, Any]]: resultado.append(row) return resultado + + @staticmethod + def parse_pensoes_instituidas(xml_string: str) -> list[dict[str, Any]]: + """ + Custom parser para consultaPensoesInstituidas: extrai dados do + ArrayPensoesInstituidas. + + Args: + xml_string (str): SOAP XML response. + + Returns: + list[dict[str, str | None]]: Lista de registros de pensões instituídas. + """ + ns = { + "soapenv": "http://schemas.xmlsoap.org/soap/envelope/", + "ns2": "http://tipo.servico.wssiapenet", + } + root = ET.fromstring(xml_string) + body = root.find("soapenv:Body", ns) + if body is None: + return [] + + resultado = [] + for item in body.findall(".//ns2:DadosPensoesInstituidas", ns): + registro = {} + for elem in item: + tag = elem.tag.split("}")[-1] + registro[tag] = elem.text.strip() if elem.text else None + resultado.append(registro) + + return resultado diff --git a/airflow_lappis/templates/siape/consultaPensoesInstituidas.xml.j2 b/airflow_lappis/templates/siape/consultaPensoesInstituidas.xml.j2 new file mode 100644 index 00000000..d138d88c --- /dev/null +++ b/airflow_lappis/templates/siape/consultaPensoesInstituidas.xml.j2 @@ -0,0 +1,15 @@ + + + + + + {{ siglaSistema }} + {{ nomeSistema }} + {{ senha }} + {{ cpf }} + {{ codOrgao }} + {{ parmExistPag }} + {{ parmTipoVinculo }} + + + From e835da965e16cce40c6fb3a2169aeb8cc662f6c0 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Thu, 12 Jun 2025 12:30:52 -0300 Subject: [PATCH 128/317] hotfix/pensoes-instituidas --- .../pensoes_instituidas_siape_ingest_dag.py | 23 +++++++++++++++---- airflow_lappis/plugins/cliente_siape.py | 12 +++++++--- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/pensoes_instituidas_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/pensoes_instituidas_siape_ingest_dag.py index 2e49ceca..6a2c2fa3 100644 --- a/airflow_lappis/dags/data_ingest/pensoes_instituidas_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/pensoes_instituidas_siape_ingest_dag.py @@ -1,5 +1,6 @@ import os import logging +import requests from datetime import datetime, timedelta from airflow.decorators import dag, task from postgres_helpers import get_postgres_conn @@ -56,9 +57,15 @@ def fetch_and_store_pensoes_instituidas() -> None: logging.warning(f"Nenhum dado de pensão instituída para CPF {cpf}") continue - # Adiciona CPF a cada registro - for registro in dados: + # Adiciona CPF a cada registro e gera ID único + for i, registro in enumerate(dados): registro["cpf"] = cpf + # Cria ID único baseado em CPF + índice + campos únicos + base_id = f"{cpf}_{i}" + cpf_pensionista = registro.get("cpfPensionista", "") + matricula_pensionista = registro.get("matriculaPensionista", "") + identificador = f"{base_id}_{cpf_pensionista}_{matricula_pensionista}" + registro["id_registro"] = identificador if dados: # Usa o primeiro registro para criar/ajustar a estrutura da tabela @@ -71,15 +78,21 @@ def fetch_and_store_pensoes_instituidas() -> None: db.insert_data( dados, table_name="pensoes_instituidas", - conflict_fields=["cpf"], - primary_key=["cpf"], + conflict_fields=["id_registro"], + primary_key=["id_registro"], schema="siape", ) logging.info( - f"Inseridos {len(dados)} registros de pensões instituídas: {cpf}" + f"Inseridos {len(dados)} registros de pensões para CPF {cpf}" ) + except requests.exceptions.HTTPError as e: + if "500" in str(e): + logging.warning(f"Servidor retornou erro 500 para CPF {cpf}") + else: + logging.error(f"Erro HTTP ao processar CPF {cpf}: {e}") + continue except Exception as e: logging.error(f"Erro ao processar CPF {cpf}: {e}") continue diff --git a/airflow_lappis/plugins/cliente_siape.py b/airflow_lappis/plugins/cliente_siape.py index 87f3046c..e72fa71e 100755 --- a/airflow_lappis/plugins/cliente_siape.py +++ b/airflow_lappis/plugins/cliente_siape.py @@ -276,7 +276,7 @@ def parse_pensoes_instituidas(xml_string: str) -> list[dict[str, Any]]: """ ns = { "soapenv": "http://schemas.xmlsoap.org/soap/envelope/", - "ns2": "http://tipo.servico.wssiapenet", + "ns1": "http://tipo.servico.wssiapenet", } root = ET.fromstring(xml_string) body = root.find("soapenv:Body", ns) @@ -284,11 +284,17 @@ def parse_pensoes_instituidas(xml_string: str) -> list[dict[str, Any]]: return [] resultado = [] - for item in body.findall(".//ns2:DadosPensoesInstituidas", ns): + + # Busca por PensoesInstituidas dentro da estrutura ArrayPensoesInstituidas + pensoes_items = body.findall(".//ns1:PensoesInstituidas", ns) + + for item in pensoes_items: registro = {} for elem in item: tag = elem.tag.split("}")[-1] - registro[tag] = elem.text.strip() if elem.text else None + # Pula elementos complexos como arrayFichaFinanceira + if tag != "arrayFichaFinanceira": + registro[tag] = elem.text.strip() if elem.text else None resultado.append(registro) return resultado From 75190df91b3735fcc2bf755f9b4679a74321251b Mon Sep 17 00:00:00 2001 From: VictorSzk Date: Fri, 13 Jun 2025 14:42:03 +0000 Subject: [PATCH 129/317] Aposentadorias --- .../gold/aposentadorias_resumo.sql | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/aposentadorias_resumo.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/aposentadorias_resumo.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/aposentadorias_resumo.sql new file mode 100644 index 00000000..545b917d --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/aposentadorias_resumo.sql @@ -0,0 +1,35 @@ +-- Tabela contendo os aposentados e tempo de serviço +-- Granularidade: cpf +with + + aposentados_extract as ( + select distinct + cpf, + nome_pessoa, + dt_ocorr_ingresso_serv_publico, + dt_ocorr_aposentadoria, + to_date( + to_char(dt_ocorr_aposentadoria, 'YYYY-MM'), 'YYYY-MM' + ) as mes_aposentadoria, + nome_situacao_funcional, + nome_ocorr_aposentadoria, + nome_cargo, + sigla_nivel_cargo, + cod_classe || '-' || cod_padrao as classe_padrao + from {{ ref("servidores_detalhados") }} sd + where dt_ocorr_aposentadoria is not null + ) + +select + *, + age(dt_ocorr_aposentadoria, dt_ocorr_ingresso_serv_publico) as age, + extract( + year from age(dt_ocorr_aposentadoria, dt_ocorr_ingresso_serv_publico) + ) as diff_anos, + extract( + month from age(dt_ocorr_aposentadoria, dt_ocorr_ingresso_serv_publico) + ) as diff_meses, + extract( + days from age(dt_ocorr_aposentadoria, dt_ocorr_ingresso_serv_publico) + ) as diff_dias +from aposentados_extract From fb18015da920bfe4fd69103f8bb87edaf0d541cf Mon Sep 17 00:00:00 2001 From: Mateus de Castro Date: Fri, 20 Jun 2025 16:32:35 +0000 Subject: [PATCH 130/317] =?UTF-8?q?Tabelas=20silvers=20(correla=C3=A7?= =?UTF-8?q?=C3=A3o=20entre=20unidades=20uorgs,=20correla=C3=A7=C3=A3o=20en?= =?UTF-8?q?tre=20cargos=20siape=20e=20siorg)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../silver/quantitativo_alocados_ocupados.sql | 92 +++++++++++++++++++ .../silver/tabela_correlacao_cargos.sql | 70 ++++++++++++++ .../unidades_organizacionais_siorg_siape.sql | 48 ++++++++++ 3 files changed, 210 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/quantitativo_alocados_ocupados.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/tabela_correlacao_cargos.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/unidades_organizacionais_siorg_siape.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/quantitativo_alocados_ocupados.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/quantitativo_alocados_ocupados.sql new file mode 100644 index 00000000..a76588fa --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/quantitativo_alocados_ocupados.sql @@ -0,0 +1,92 @@ +with + siape_sem_duplicatas as (select distinct * from {{ ref("dados_funcionais") }}), + siorg_sem_duplicatas as ( + select distinct * from {{ ref("estrutura_organizacional_cargos") }} + ), + + codigos_siorg as ( + select funcao, nomeunidade, siglaunidade, denominacao, count(*) as qtd_vagas_cargo + from siorg_sem_duplicatas + group by funcao, nomeunidade, siglaunidade, denominacao + ), + + codigos_siape as ( + select + cod_funcao, + nome_uorg_exercicio, + sigla_uorg_exercicio, + nome_cargo, + count(*) as qtd_vagas_ocupadas + from siape_sem_duplicatas + where cod_funcao is not null and dt_ocorr_aposentadoria is null + group by cod_funcao, nome_uorg_exercicio, sigla_uorg_exercicio, nome_cargo + ), + + codigo_siorg_combinado as ( + select + replace(funcao, ' ', '') as funcao, + nomeunidade, + case + when siglaunidade = 'GABIN-IPEA' then 'GABIN' else siglaunidade + end as siglaunidade, + denominacao, + substring(replace(funcao, ' ', ''), 1, 1) || substring( + replace(funcao, ' ', ''), length(replace(funcao, ' ', '')) - 2, 3 + ) as codigo_combinacao_siorg, + qtd_vagas_cargo + from codigos_siorg + ), + + codigo_siape_combinado as ( + select + cod_funcao, + nome_uorg_exercicio, + sigla_uorg_exercicio, + nome_cargo, + substring(cod_funcao, 1, 1) || substring( + cod_funcao, length(cod_funcao) - 2, 3 + ) as codigo_combinacao_siape, + qtd_vagas_ocupadas + from codigos_siape + ), + + primeira_correlacao as ( + select + *, + case + when + siorg.codigo_combinacao_siorg is not null + and siape.codigo_combinacao_siape is not null + then 'inner' + when + siorg.codigo_combinacao_siorg is not null + and siape.codigo_combinacao_siape is null + then 'left' + when + siorg.codigo_combinacao_siorg is null + and siape.codigo_combinacao_siape is not null + then 'right' + end as tipo_correlacao + from codigo_siorg_combinado as siorg + full join + codigo_siape_combinado as siape + on siorg.codigo_combinacao_siorg = siape.codigo_combinacao_siape + and siorg.siglaunidade = siape.sigla_uorg_exercicio + ) + +select + cod_funcao as codigo_siape, + funcao as codigo_siorg, + coalesce(nomeunidade, nome_uorg_exercicio) as nomeunidade, + coalesce(siglaunidade, sigla_uorg_exercicio) as siglaunidade, + coalesce(denominacao, nome_cargo) as nome_cargo, + qtd_vagas_cargo, + coalesce(qtd_vagas_ocupadas, 0) as qtd_vagas_ocupadas, + case + when qtd_vagas_cargo is null + then null + when qtd_vagas_ocupadas is null + then qtd_vagas_cargo + else (qtd_vagas_cargo - qtd_vagas_ocupadas) + end as qtd_cargos_vagos +from primeira_correlacao diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/tabela_correlacao_cargos.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/tabela_correlacao_cargos.sql new file mode 100644 index 00000000..b9ca9eaf --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/tabela_correlacao_cargos.sql @@ -0,0 +1,70 @@ +with + codigos_siorg as ( + select distinct + replace(funcao, ' ', '') as funcao, + nomeunidade, + case + when siglaunidade = 'GABIN-IPEA' then 'GABIN' else siglaunidade + end as siglaunidade, + denominacao + from {{ ref("estrutura_organizacional_cargos") }} + ), + + codigos_siape as ( + select distinct + cod_funcao, + nome_uorg_exercicio, + sigla_uorg_exercicio, + nome_cargo, + matricula_siape, + substring(cod_funcao, 1, 1) + || substring(cod_funcao, length(cod_funcao) - 2, 3) as codigo_combinacao_siape + from {{ ref("dados_funcionais") }} + where cod_funcao is not null and dt_ocorr_aposentadoria is null + ), + + codigo_siorg_combinado as ( + select + *, + substring(funcao, 1, 1) + || substring(funcao, length(funcao) - 2, 3) as codigo_combinacao_siorg + from codigos_siorg + ), + + primeira_correlacao as ( + select + *, + case + when + siorg.codigo_combinacao_siorg is not null + and siape.codigo_combinacao_siape is not null + then 'inner' + when + siorg.codigo_combinacao_siorg is not null + and siape.codigo_combinacao_siape is null + then 'left' + when + siorg.codigo_combinacao_siorg is null + and siape.codigo_combinacao_siape is not null + then 'right' + end as tipo_correlacao + from codigo_siorg_combinado as siorg + full join + codigos_siape as siape + on siorg.codigo_combinacao_siorg = siape.codigo_combinacao_siape + and siorg.siglaunidade = siape.sigla_uorg_exercicio + ), + + tabela_correlacao_cargos as ( + select + cod_funcao as codigo_siape, + funcao as codigo_siorg, + coalesce(nomeunidade, nome_uorg_exercicio) as nomeunidade, + coalesce(siglaunidade, sigla_uorg_exercicio) as siglaunidade, + coalesce(denominacao, nome_cargo) as nome_cargo, + matricula_siape + from primeira_correlacao + ) + +select * +from tabela_correlacao_cargos diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/unidades_organizacionais_siorg_siape.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/unidades_organizacionais_siorg_siape.sql new file mode 100644 index 00000000..263e7893 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/unidades_organizacionais_siorg_siape.sql @@ -0,0 +1,48 @@ +with + preparacao as ( + select distinct + du.codigo_orgao::integer as codigo_orgao, + du.codigo_orgao_uorg as combinacao_codigo_lista, + (right(du.codigo_orgao_uorg, 7))::integer as codigo_lista_uorg, + du.sigla_uorg as sigla_uorg + from {{ ref("dados_uorg") }} du + ), + + join_lista_uorgo_dados_uorg as ( + select + p.codigo_orgao, + p.combinacao_codigo_lista, + p.codigo_lista_uorg, + p.sigla_uorg, + lu.dt_ultima_transacao, + lu.nome as nome_unidade + from preparacao p + join {{ ref("lista_uorgs") }} lu on p.codigo_lista_uorg = lu.codigo + ), + + unidade_organizacional as ( + select distinct + *, case when sigla = 'GABIN-IPEA' then 'GABIN' else sigla end as sigla_unidade + from {{ ref("unidade_organizacional") }} + ), + + tabela_corralacao_uorgs as ( + select + coalesce(a.nome_unidade, uo.nome) as nome_unidade, + coalesce(a.sigla_uorg, sigla_unidade) as sigla_uorg, + a.codigo_lista_uorg as codigo_unidade_siape, + uo.codigounidade as codigo_unidade_siorg, + case + when a.nome_unidade is null and uo.nome is not null + then 'apenas_siorg' + when a.nome_unidade is not null and uo.nome is null + then 'apenas_siape' + when a.nome_unidade is not null and uo.nome is not null + then 'ambos' + end as tipo_correlacao + from join_lista_uorgo_dados_uorg a + full join unidade_organizacional uo on a.sigla_uorg = uo.sigla_unidade + ) + +select * +from tabela_corralacao_uorgs From 0346142d00dcb764cb58b5a53d1fc25c2f2cfe9d Mon Sep 17 00:00:00 2001 From: VictorSzk Date: Mon, 23 Jun 2025 13:27:30 +0000 Subject: [PATCH 131/317] Terceirizados --- .../pessoas_dbt/bronze/terceirizados.sql | 20 +++++++++++++++++++ .../gold/aposentadorias_resumo.sql | 4 +--- .../dags/dbt/ipea/models/sources.yml | 1 + 3 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/terceirizados.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/terceirizados.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/terceirizados.sql new file mode 100644 index 00000000..fbfab585 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/terceirizados.sql @@ -0,0 +1,20 @@ +select + id, + contrato_id, + substring(usuario, '(.+) - ') as cpf, + substring(usuario, '- (.+)') as nome, + funcao_id, + descricao_complementar, + jornada::numeric as jornada, + unidade, + replace(replace(salario, '.', ''), ',', '.')::numeric(15, 2) as salario, + replace(replace(custo, '.', ''), ',', '.')::numeric(15, 2) as custo, + escolaridade_id, + to_date(data_inicio, 'YYYY-mm-dd') as data_inicio, + to_date(data_fim, 'YYYY-mm-dd') as data_fim, + situacao, + replace(replace(aux_transporte, '.', ''), ',', '.')::numeric(15, 2) as aux_transporte, + replace(replace(vale_alimentacao, '.', ''), ',', '.')::numeric( + 15, 2 + ) as vale_alimentacao +from {{ source("compras_gov", "terceirizados") }} diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/aposentadorias_resumo.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/aposentadorias_resumo.sql index 545b917d..4b306347 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/aposentadorias_resumo.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/aposentadorias_resumo.sql @@ -8,9 +8,7 @@ with nome_pessoa, dt_ocorr_ingresso_serv_publico, dt_ocorr_aposentadoria, - to_date( - to_char(dt_ocorr_aposentadoria, 'YYYY-MM'), 'YYYY-MM' - ) as mes_aposentadoria, + date_trunc('month', dt_ocorr_aposentadoria) as mes_aposentadoria, nome_situacao_funcional, nome_ocorr_aposentadoria, nome_cargo, diff --git a/airflow_lappis/dags/dbt/ipea/models/sources.yml b/airflow_lappis/dags/dbt/ipea/models/sources.yml index f0b5eb89..edf337f7 100644 --- a/airflow_lappis/dags/dbt/ipea/models/sources.yml +++ b/airflow_lappis/dags/dbt/ipea/models/sources.yml @@ -8,6 +8,7 @@ sources: - name: faturas - name: empenhos - name: cronograma + - name: terceirizados - name: siafi schema: siafi From 6ad6fd7bf68f287dc7ccb89d693a28a37fb184d1 Mon Sep 17 00:00:00 2001 From: VictorSzk Date: Wed, 9 Jul 2025 16:41:14 +0000 Subject: [PATCH 132/317] Fix/silver afastamentos --- .../silver/afastamento_consolidado.sql | 108 ++++++++++-------- airflow_lappis/plugins/cliente_siafi.py | 2 +- airflow_lappis/plugins/cliente_siape.py | 11 +- 3 files changed, 67 insertions(+), 54 deletions(-) diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/afastamento_consolidado.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/afastamento_consolidado.sql index 0e7c29ee..04ae52dd 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/afastamento_consolidado.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/afastamento_consolidado.sql @@ -1,51 +1,61 @@ -select - adiantamento_salario_ferias, - ano_exercicio, - dt_fim, - dt_fim_aquisicao, - dt_ini, - dt_inicio_aquisicao, - dt_inicio_ferias_interrompidas, - dias_restantes, - gratificacao_natalina, - numero_parcela, - parcela_continuacao_interrupcao, - parcela_interrompida, - qtde_dias, - cpf, - cod_diploma_afastamento, - cod_ocorrencia, - dt_publicacao_afastamento, - desc_diploma_afastamento, - desc_ocorrencia, - numero_diploma_afastamento, - gr_matricula, - 'dados_afastamento' as origem_dados -- identificar a fonte -from {{ ref("dados_afastamento") }} +with -union all + dados_afastamento_totais as ( + select distinct + adiantamento_salario_ferias, + ano_exercicio, + dt_fim, + dt_fim_aquisicao, + dt_ini, + dt_inicio_aquisicao, + dt_inicio_ferias_interrompidas, + dias_restantes, + gratificacao_natalina, + numero_parcela, + parcela_continuacao_interrupcao, + parcela_interrompida, + qtde_dias, + cpf, + cod_diploma_afastamento, + cod_ocorrencia, + dt_publicacao_afastamento, + desc_diploma_afastamento, + desc_ocorrencia, + numero_diploma_afastamento, + gr_matricula, + 'dados_afastamento' as origem_dados -- identificar a fonte + from {{ ref("dados_afastamento") }} -select - adiantamento_salario_ferias, - ano_exercicio, - dt_fim, - dt_fim_aquisicao, - dt_ini, - dt_inicio_aquisicao, - dt_inicio_ferias_interrompidas, - dias_restantes, - gratificacao_natalina, - numero_parcela, - parcela_continuacao_interrupcao, - parcela_interrompida, - qtde_dias, - cpf, - cod_diploma_afastamento, - cod_ocorrencia, - dt_publicacao_afastamento, - desc_diploma_afastamento, - desc_ocorrencia, - numero_diploma_afastamento, - null as gr_matricula, -- não tem na afastamneto historico ... - 'afastamento_historico' as origem_dados -- identificar a fonte -from {{ ref("afastamento_historico") }} + union all + + select distinct + adiantamento_salario_ferias, + ano_exercicio, + dt_fim, + dt_fim_aquisicao, + dt_ini, + dt_inicio_aquisicao, + dt_inicio_ferias_interrompidas, + dias_restantes, + gratificacao_natalina, + numero_parcela, + parcela_continuacao_interrupcao, + parcela_interrompida, + qtde_dias, + cpf, + cod_diploma_afastamento, + cod_ocorrencia, + dt_publicacao_afastamento, + desc_diploma_afastamento, + desc_ocorrencia, + numero_diploma_afastamento, + null as gr_matricula, -- não tem na afastamneto historico ... + 'afastamento_historico' as origem_dados -- identificar a fonte + from {{ ref("afastamento_historico") }} + ), + + nomes_dt as (select distinct nome_pessoa, cpf from {{ ref("dados_pessoais") }}) + +select * +from dados_afastamento_totais +left join nomes_dt using (cpf) diff --git a/airflow_lappis/plugins/cliente_siafi.py b/airflow_lappis/plugins/cliente_siafi.py index fa73eb28..b8acd5b1 100644 --- a/airflow_lappis/plugins/cliente_siafi.py +++ b/airflow_lappis/plugins/cliente_siafi.py @@ -49,7 +49,7 @@ def _criar_cliente_soap(self, ano: int, endpoint: str) -> Optional[Client]: return None session = Session() - session.verify = self.cert_path + session.verify = True # Verificar certificados SSL session.cert = (self.cert_path, self.key_path) transport = Transport(session=session) diff --git a/airflow_lappis/plugins/cliente_siape.py b/airflow_lappis/plugins/cliente_siape.py index e72fa71e..39373856 100755 --- a/airflow_lappis/plugins/cliente_siape.py +++ b/airflow_lappis/plugins/cliente_siape.py @@ -88,7 +88,8 @@ def render_xml(self, template_name: str, context: Dict[str, str]) -> str: str: Rendered XML string. """ template = self.env.get_template(template_name) - return template.render(context) + rendered_xml: str = template.render(context) + return rendered_xml def enviar_soap(self, xml: str) -> str: """ @@ -104,7 +105,8 @@ def enviar_soap(self, xml: str) -> str: ClienteSiape.SOAP_ENDPOINT, headers=self.headers, data=xml ) response.raise_for_status() - return response.text + response_text: str = response.text + return response_text def call(self, template_name: str, context: Dict[str, str]) -> str: """ @@ -117,8 +119,9 @@ def call(self, template_name: str, context: Dict[str, str]) -> str: Returns: str: The raw XML response. """ - xml = self.render_xml(template_name, context) - return self.enviar_soap(xml) + xml: str = self.render_xml(template_name, context) + soap_response: str = self.enviar_soap(xml) + return soap_response @staticmethod def parse_xml_to_dict(xml_string: str) -> Dict[str, str]: From f0fc57ac8c0b8a116b3724213795dc00bb390bba Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Sat, 12 Jul 2025 14:56:23 +0000 Subject: [PATCH 133/317] feat/dt_ingest dados funcionais --- .../dags/data_ingest/dados_funcionais_siape_ingest_dag.py | 1 + 1 file changed, 1 insertion(+) diff --git a/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py index a134287b..0dbfd6d9 100644 --- a/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py @@ -53,6 +53,7 @@ def fetch_and_store_dados_funcionais() -> None: continue dados["cpf"] = cpf + dados["dt_ingest"] = datetime.now().isoformat() db.alter_table( data=dados, From 554918932888c235cfc4e5cbc335f7f9ef2b004e Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Wed, 16 Jul 2025 16:32:12 -0300 Subject: [PATCH 134/317] fix/dbt-lista-servidores --- .../dbt/ipea/models/pessoas_dbt/bronze/lista_servidores.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_servidores.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_servidores.sql index 5b9c7873..b599073c 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_servidores.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_servidores.sql @@ -1,6 +1,6 @@ with lista_servidores as ( - select cpf, dt_ultima_transacao, cod_uorg + select cpf, dataultimatransacao as dt_ultima_transacao, coduorg as cod_uorg from {{ source("siape", "lista_servidores") }} ) From c56194a9f69bbc7f62f29cb4d493e9794da81ccc Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Sun, 20 Jul 2025 18:49:38 -0300 Subject: [PATCH 135/317] feat(siape): implementa parsing de multiplos registros para dados funcionais --- .../dados_funcionais_siape_ingest_dag.py | 2 +- .../helpers/dados_funcionais_handler.py | 142 ++++++++++++++++++ airflow_lappis/plugins/cliente_siape.py | 38 +++++ 3 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 airflow_lappis/helpers/dados_funcionais_handler.py diff --git a/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py index 0dbfd6d9..3eb5cad2 100644 --- a/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py @@ -46,7 +46,7 @@ def fetch_and_store_dados_funcionais() -> None: resposta_xml = cliente_siape.call( "consultaDadosFuncionais.xml.j2", context ) - dados = ClienteSiape.parse_xml_to_dict(resposta_xml) + dados = ClienteSiape.parse_dado_funcional(resposta_xml) if not dados: logging.warning(f"Nenhum dado funcional encontrado para CPF {cpf}") diff --git a/airflow_lappis/helpers/dados_funcionais_handler.py b/airflow_lappis/helpers/dados_funcionais_handler.py new file mode 100644 index 00000000..d56d3154 --- /dev/null +++ b/airflow_lappis/helpers/dados_funcionais_handler.py @@ -0,0 +1,142 @@ +""" +Handler para processamento de dados funcionais do SIAPE. +Responsável por filtrar e selecionar registros funcionais ativos. +""" + +import logging +from typing import Dict, List +import xml.etree.ElementTree as ET + + +class DadosFuncionaisHandler: + """Handler para processar dados funcionais do SIAPE.""" + + @staticmethod + def extract_dados_funcionais_elements(xml_string: str) -> List[ET.Element]: + """Extrai elementos DadosFuncionais do XML.""" + ns = { + "soap": "http://schemas.xmlsoap.org/soap/envelope/", + "soapenv": "http://schemas.xmlsoap.org/soap/envelope/", + } + root = ET.fromstring(xml_string) + + # Busca o body usando ambos os namespaces possíveis + body = root.find("soap:Body", ns) or root.find("soapenv:Body", ns) + if body is None: + logging.warning("SOAP Body não encontrado no XML") + return [] + + # Busca todos os elementos DadosFuncionais usando iter() que ignora namespaces + dados_funcionais_items = [] + for elem in body.iter(): + tag = elem.tag.split("}")[-1] if "}" in elem.tag else elem.tag + if tag == "DadosFuncionais": + dados_funcionais_items.append(elem) + + return dados_funcionais_items + + @staticmethod + def convert_elements_to_registros( + elementos: List[ET.Element], + ) -> List[Dict[str, str | None]]: + """Converte elementos XML em dicionários de registros.""" + registros = [] + for item in elementos: + registro = {} + for elem in item: + tag = elem.tag.split("}")[-1] if "}" in elem.tag else elem.tag + registro[tag] = ( + elem.text.strip() if elem.text and elem.text.strip() else None + ) + registros.append(registro) + return registros + + @staticmethod + def select_best_registro( + registros: List[Dict[str, str | None]], + ) -> Dict[str, str | None]: + """Seleciona o melhor registro funcional baseado nas regras de negócio.""" + if not registros: + return {} + + # Filtra registros ativos (sem dataOcorrExclusao ou com valor vazio/None) + registros_ativos = [ + r + for r in registros + if not r.get("dataOcorrExclusao") or r.get("dataOcorrExclusao") == "" + ] + + logging.info(f"Registros ativos (sem dataOcorrExclusao): {len(registros_ativos)}") + + if not registros_ativos: + return DadosFuncionaisHandler._handle_no_active_records(registros) + + if len(registros_ativos) == 1: + return DadosFuncionaisHandler._handle_single_active_record( + registros_ativos[0] + ) + + return DadosFuncionaisHandler._handle_multiple_active_records(registros_ativos) + + @staticmethod + def _handle_no_active_records( + registros: List[Dict[str, str | None]], + ) -> Dict[str, str | None]: + """Lida com o caso onde não há registros ativos.""" + logging.warning( + "Nenhum registro funcional ativo encontrado (todos têm dataOcorrExclusao)" + ) + # Se não há registros ativos, retorna o mais recente + # baseado em dataIngressoFuncao + registros_com_data = [r for r in registros if r.get("dataIngressoFuncao")] + if registros_com_data: + registros_ordenados = sorted( + registros_com_data, + key=lambda x: x.get("dataIngressoFuncao") or "00000000", + reverse=True, + ) + data_ingresso = registros_ordenados[0].get("dataIngressoFuncao") + logging.info( + f"Retornando registro mais recente: " + f"dataIngressoFuncao={data_ingresso}" + ) + return registros_ordenados[0] + else: + # Se não há datas de ingresso, retorna o primeiro + return registros[0] if registros else {} + + @staticmethod + def _handle_single_active_record( + registro: Dict[str, str | None], + ) -> Dict[str, str | None]: + """Lida com o caso onde há apenas um registro ativo.""" + matricula = registro.get("matriculaSiape") + logging.info(f"Retornando único registro ativo: matricula={matricula}") + return registro + + @staticmethod + def _handle_multiple_active_records( + registros_ativos: List[Dict[str, str | None]], + ) -> Dict[str, str | None]: + """Lida com o caso onde há múltiplos registros ativos.""" + # Se há múltiplos registros ativos, retorna o mais recente + # baseado em dataIngressoFuncao + registros_ativos_com_data = [ + r for r in registros_ativos if r.get("dataIngressoFuncao") + ] + + if registros_ativos_com_data: + registro_mais_recente = max( + registros_ativos_com_data, + key=lambda x: x.get("dataIngressoFuncao") or "00000000", + ) + else: + # Se nenhum tem data de ingresso, pega o primeiro ativo + registro_mais_recente = registros_ativos[0] + + logging.info( + f"Múltiplos registros ativos encontrados, selecionando o mais recente: " + f"dataIngressoFuncao={registro_mais_recente.get('dataIngressoFuncao', 'N/A')}, " # noqa: E501 + f"matricula={registro_mais_recente.get('matriculaSiape', 'N/A')}" + ) + return registro_mais_recente diff --git a/airflow_lappis/plugins/cliente_siape.py b/airflow_lappis/plugins/cliente_siape.py index 39373856..a99deb8d 100755 --- a/airflow_lappis/plugins/cliente_siape.py +++ b/airflow_lappis/plugins/cliente_siape.py @@ -1,8 +1,10 @@ import os +import logging from typing import Dict, Any import requests import xml.etree.ElementTree as ET from jinja2 import Environment, FileSystemLoader +from dados_funcionais_handler import DadosFuncionaisHandler class ClienteSiape: @@ -301,3 +303,39 @@ def parse_pensoes_instituidas(xml_string: str) -> list[dict[str, Any]]: resultado.append(registro) return resultado + + @staticmethod + def parse_dado_funcional(xml_string: str) -> Dict[str, str | None]: + """ + Custom parser para consultaDadosFuncionais: extrai múltiplos DadosFuncionais + e retorna apenas o registro ativo (sem dataOcorrExclusao). + + Args: + xml_string (str): SOAP XML response contendo múltiplos DadosFuncionais. + + Returns: + Dict[str, str]: Registro funcional ativo (mais atual). + """ + try: + handler = DadosFuncionaisHandler() + + # Extrai elementos DadosFuncionais + dados_funcionais_items = handler.extract_dados_funcionais_elements(xml_string) + + if not dados_funcionais_items: + logging.warning("Nenhum elemento DadosFuncionais encontrado") + return {} + + # Converte elementos para registros + registros = handler.convert_elements_to_registros(dados_funcionais_items) + logging.info(f"Encontrados {len(registros)} registros funcionais") + + # Seleciona o melhor registro + return handler.select_best_registro(registros) + + except ET.ParseError as e: + logging.error(f"Erro ao fazer parse do XML: {e}") + return {} + except Exception as e: + logging.error(f"Erro inesperado no parse_dado_funcional: {e}") + return {} From 2304b2e666b05a7572f3aa2a26958de06fa67b23 Mon Sep 17 00:00:00 2001 From: Mateus de Castro Date: Tue, 22 Jul 2025 18:10:56 +0000 Subject: [PATCH 136/317] feat(gold):nova tabela gold --- .../bronze/unidade_organizacional.sql | 7 +- .../models/pessoas_dbt/gold/hierarquia.sql | 151 ++++++++++++++++++ 2 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/hierarquia.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/unidade_organizacional.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/unidade_organizacional.sql index b97a3b51..fd3c10dc 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/unidade_organizacional.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/unidade_organizacional.sql @@ -23,13 +23,16 @@ with recursive unidades_raiz as (select '7' as codigounidade_raiz), hierarquia as ( - select f.*, 1 as ordem_grandeza + select f.*, 1 as ordem_grandeza, sigla as caminho_unidade from fonte f join unidades_raiz r on f.codigounidade = r.codigounidade_raiz union all - select f.*, h.ordem_grandeza + 1 as ordem_grandeza + select + f.*, + h.ordem_grandeza + 1 as ordem_grandeza, + h.caminho_unidade || '-' || lpad(f.sigla::text, 5, '0') as caminho_unidade from fonte f join hierarquia h on f.codigounidadepai = h.codigounidade ) diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/hierarquia.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/hierarquia.sql new file mode 100644 index 00000000..1ff2e005 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/hierarquia.sql @@ -0,0 +1,151 @@ +with + + correcao_funcao as ( + select *, replace(funcao, ' ', '') as funcao_sigla + from {{ ref("estrutura_organizacional_cargos") }} + ), + + codigos_siorg as ( + select distinct + funcao_sigla, + eorg.nomeunidade, + eorg.codigounidade, + eorg.ordem_grandeza, + eorg.denominacao, + uo.codigounidadepai, + uo.caminho_unidade, + case + when eorg.siglaunidade = 'GABIN-IPEA' then 'GABIN' else siglaunidade + end as siglaunidade, + substring(funcao_sigla, length(funcao_sigla) - 2, 1) as categoria_cargo, + -- hierarquia do cargo está sendo definida a partir da fórmula: + -- (categoria do cargo * 1000) - nível do cargo + -- quanto menor a hierarquia, maior o cargo + right(funcao_sigla, 2) as nivel_cargo, + cast(substring(funcao_sigla, length(funcao_sigla) - 2, 1) as int) * 1000 + - cast(right(funcao, 2) as int) as hierarquia_cargo + from correcao_funcao as eorg + inner join + {{ ref("unidade_organizacional") }} as uo + on eorg.codigounidade = uo.codigounidade + ), + + codigos_siape as ( + select distinct + df.cod_funcao, + df.nome_uorg_exercicio, + df.sigla_uorg_exercicio, + df.nome_cargo, + df.matricula_siape, + df.cpf, + df.cpf_chefia_imediata, + df.cod_situacao_funcional, + df.nome_situacao_funcional, + dp.nome_pessoa, + dp.dt_nascimento, + dp.nome_sexo, + dp.nome_estado_civil, + dp.nome_nacionalidade, + dp.nome_cor, + dp.uf_nascimento, + dp.nome_municipio_nascimento, + uo.codigounidade as codigounidade_alternativa, + uo.caminho_unidade as caminho_unidade_alternativa, + uo.codigounidadepai as codigounidadepai_alternativa, + uo.ordem_grandeza as ordem_grandeza_alternativa, + substring(df.cod_funcao, 1, 1) || substring( + df.cod_funcao, length(df.cod_funcao) - 2, 3 + ) as codigo_combinacao_siape + from {{ ref("dados_funcionais") }} as df + left join {{ ref("dados_pessoais") }} as dp on df.cpf = dp.cpf + left join + {{ ref("unidade_organizacional") }} as uo + on df.sigla_uorg_exercicio = uo.sigla + where dt_ocorr_aposentadoria is null + ), + + -- select count(*) from codigos_siape; + codigo_siorg_combinado as ( + select + *, + substring(funcao_sigla, 1, 1) || substring( + funcao_sigla, length(funcao_sigla) - 2, 3 + ) as codigo_combinacao_siorg + from codigos_siorg + ), + + primeira_correlacao as ( + select + *, + case + when + siorg.codigo_combinacao_siorg is not null + and siape.codigo_combinacao_siape is not null + then 'inner' + when + siorg.codigo_combinacao_siorg is not null + and siape.codigo_combinacao_siape is null + then 'left' + when + siorg.codigo_combinacao_siorg is null + and siape.codigo_combinacao_siape is not null + then 'right' + end as tipo_correlacao + from codigo_siorg_combinado as siorg + full join + codigos_siape as siape + on siorg.codigo_combinacao_siorg = siape.codigo_combinacao_siape + and siorg.siglaunidade = siape.sigla_uorg_exercicio + ), + + -- select count(*) from primeira_correlacao + tabela_correlacao_cargos as ( + select distinct + pr.cod_funcao as codigo_siape, + pr.funcao_sigla as codigo_siorg, + pr.codigo_combinacao_siape, + pr.codigo_combinacao_siorg, + pr.matricula_siape as matricula_siape, + pr.cpf as cpf, + pr.cpf_chefia_imediata as cpf_chefia_imediata, + pr.cod_situacao_funcional as cod_situacao_funcional, + pr.nome_situacao_funcional as nome_situacao_funcional, + pr.hierarquia_cargo as hierarquia_cargo, + pr.nome_pessoa as servidor, + pr.dt_nascimento as dt_nascimento, + pr.nome_sexo as nome_sexo, + pr.nome_estado_civil as nome_estado_civil, + pr.nome_nacionalidade as nome_nacionalidade, + pr.nome_cor as nome_cor, + pr.uf_nascimento as uf_nascimento, + pr.nome_municipio_nascimento as nome_municipio_nascimento, + dp.nome_pessoa as nome_chefia, + coalesce( + cast(pr.codigounidade as text), cast(pr.codigounidade_alternativa as text) + ) as codigounidade, + coalesce( + cast(pr.codigounidadepai as text), + cast(pr.codigounidadepai_alternativa as text) + ) as codigounidadepai, + coalesce( + cast(pr.caminho_unidade as text), + cast(pr.caminho_unidade_alternativa as text) + ) as caminho_unidade, + coalesce( + cast(pr.ordem_grandeza as text), + cast(pr.ordem_grandeza_alternativa as text) + ) as ordem_grandeza, + coalesce(nomeunidade, nome_uorg_exercicio) as nomeunidade, + coalesce(siglaunidade, sigla_uorg_exercicio) as siglaunidade, + coalesce(denominacao, nome_cargo) as nome_cargo, + case + when cod_situacao_funcional = '04' then 'Nomeação livre' else 'Carreira' + end as servidores_carreira + from primeira_correlacao as pr + left join {{ ref("dados_pessoais") }} as dp on pr.cpf_chefia_imediata = dp.cpf + order by caminho_unidade, hierarquia_cargo + ) + +select * +from tabela_correlacao_cargos +where nome_situacao_funcional != 'ATIVO EM OUTRO ORGAO' From 801df72e39758b9d34b93becc71d7a7945f94711 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Mon, 4 Aug 2025 11:36:02 -0300 Subject: [PATCH 137/317] feat/estrutura dags de ingestao --- airflow_lappis/dags/data_ingest/{ => admin}/drop_tables_dag.py | 0 .../{ => compras_gov}/contratos_inativos_ingest_dag.py | 0 .../dags/data_ingest/{ => compras_gov}/contratos_ingest_dag.py | 0 .../dags/data_ingest/{ => compras_gov}/cronograma_ingest_dag.py | 0 .../dags/data_ingest/{ => compras_gov}/empenhos_ingest_dag.py | 0 .../dags/data_ingest/{ => compras_gov}/faturas_ingest_dag.py | 0 .../data_ingest/{ => compras_gov}/terceirizados_ingest_dag.py | 0 .../data_ingest/{ => siafi}/nota_credito_siafi_ingest_dag.py | 0 .../data_ingest/{ => siafi}/nota_empenho_siafi_ingest_dag.py | 0 .../{ => siafi}/programacao_financeira_siafi_ingest_dag.py | 0 .../{ => siape}/dados_afastamento_historico_siape_ingest_dag.py | 0 .../{ => siape}/dados_afastamento_siape_ingest_dag.py | 0 .../data_ingest/{ => siape}/dados_curriculo_siape_ingest_dag.py | 0 .../{ => siape}/dados_dependentes_siape_ingest_dag.py | 0 .../data_ingest/{ => siape}/dados_escolares_siape_ingest_dag.py | 0 .../dags/data_ingest/{ => siape}/dados_financeiros_siape_dag.py | 0 .../{ => siape}/dados_funcionais_siape_ingest_dag.py | 0 .../dags/data_ingest/{ => siape}/dados_pa_siape_ingest_dag.py | 0 .../data_ingest/{ => siape}/dados_pessoais_siape_ingest_dag.py | 0 .../dags/data_ingest/{ => siape}/dados_uorg_siape_ingest_dag.py | 0 .../{ => siape}/lista_aposentadoria_siape_ingest_dag.py | 0 .../{ => siape}/lista_servidores_siape_ingest_dag.py | 0 .../data_ingest/{ => siape}/lista_uorgs_siape_ingest_dag.py | 0 .../{ => siape}/pensoes_instituidas_siape_ingest_dag.py | 0 .../dags/data_ingest/{ => siorg}/cargos_funcao_ingest_dag.py | 0 .../{ => siorg}/estrutura_organizacional_cargos_ingest_dag.py | 0 .../{ => siorg}/unidade_organizacional_ingest_dag.py | 0 .../{ => tesouro_gerencial}/empenhos_tesouro_ingest_dag.py | 2 +- .../{ => tesouro_gerencial}/estagios_tesouro_ingest_dag.py | 0 .../{ => tesouro_gerencial}/nc_tesouro_ingest.dag.py | 0 .../{ => tesouro_gerencial}/pf_tesouro_ingest_dag.py | 0 .../{ => transfere_gov}/notas_de_credito_ingest_dag.py | 0 .../data_ingest/{ => transfere_gov}/plano_acao_ingest_dag.py | 0 .../{ => transfere_gov}/programa_beneficiario_ingest_dag.py | 0 .../{ => transfere_gov}/programacao_financeira_ingest_dag.py | 0 .../data_ingest/{ => transfere_gov}/programas_ingest_dag.py | 0 36 files changed, 1 insertion(+), 1 deletion(-) rename airflow_lappis/dags/data_ingest/{ => admin}/drop_tables_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => compras_gov}/contratos_inativos_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => compras_gov}/contratos_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => compras_gov}/cronograma_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => compras_gov}/empenhos_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => compras_gov}/faturas_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => compras_gov}/terceirizados_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => siafi}/nota_credito_siafi_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => siafi}/nota_empenho_siafi_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => siafi}/programacao_financeira_siafi_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => siape}/dados_afastamento_historico_siape_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => siape}/dados_afastamento_siape_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => siape}/dados_curriculo_siape_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => siape}/dados_dependentes_siape_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => siape}/dados_escolares_siape_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => siape}/dados_financeiros_siape_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => siape}/dados_funcionais_siape_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => siape}/dados_pa_siape_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => siape}/dados_pessoais_siape_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => siape}/dados_uorg_siape_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => siape}/lista_aposentadoria_siape_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => siape}/lista_servidores_siape_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => siape}/lista_uorgs_siape_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => siape}/pensoes_instituidas_siape_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => siorg}/cargos_funcao_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => siorg}/estrutura_organizacional_cargos_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => siorg}/unidade_organizacional_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => tesouro_gerencial}/empenhos_tesouro_ingest_dag.py (99%) rename airflow_lappis/dags/data_ingest/{ => tesouro_gerencial}/estagios_tesouro_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => tesouro_gerencial}/nc_tesouro_ingest.dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => tesouro_gerencial}/pf_tesouro_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => transfere_gov}/notas_de_credito_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => transfere_gov}/plano_acao_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => transfere_gov}/programa_beneficiario_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => transfere_gov}/programacao_financeira_ingest_dag.py (100%) rename airflow_lappis/dags/data_ingest/{ => transfere_gov}/programas_ingest_dag.py (100%) diff --git a/airflow_lappis/dags/data_ingest/drop_tables_dag.py b/airflow_lappis/dags/data_ingest/admin/drop_tables_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/drop_tables_dag.py rename to airflow_lappis/dags/data_ingest/admin/drop_tables_dag.py diff --git a/airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py b/airflow_lappis/dags/data_ingest/compras_gov/contratos_inativos_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/contratos_inativos_ingest_dag.py rename to airflow_lappis/dags/data_ingest/compras_gov/contratos_inativos_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/contratos_ingest_dag.py b/airflow_lappis/dags/data_ingest/compras_gov/contratos_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/contratos_ingest_dag.py rename to airflow_lappis/dags/data_ingest/compras_gov/contratos_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/cronograma_ingest_dag.py b/airflow_lappis/dags/data_ingest/compras_gov/cronograma_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/cronograma_ingest_dag.py rename to airflow_lappis/dags/data_ingest/compras_gov/cronograma_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/empenhos_ingest_dag.py b/airflow_lappis/dags/data_ingest/compras_gov/empenhos_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/empenhos_ingest_dag.py rename to airflow_lappis/dags/data_ingest/compras_gov/empenhos_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/faturas_ingest_dag.py b/airflow_lappis/dags/data_ingest/compras_gov/faturas_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/faturas_ingest_dag.py rename to airflow_lappis/dags/data_ingest/compras_gov/faturas_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/terceirizados_ingest_dag.py b/airflow_lappis/dags/data_ingest/compras_gov/terceirizados_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/terceirizados_ingest_dag.py rename to airflow_lappis/dags/data_ingest/compras_gov/terceirizados_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/nota_credito_siafi_ingest_dag.py b/airflow_lappis/dags/data_ingest/siafi/nota_credito_siafi_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/nota_credito_siafi_ingest_dag.py rename to airflow_lappis/dags/data_ingest/siafi/nota_credito_siafi_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/nota_empenho_siafi_ingest_dag.py b/airflow_lappis/dags/data_ingest/siafi/nota_empenho_siafi_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/nota_empenho_siafi_ingest_dag.py rename to airflow_lappis/dags/data_ingest/siafi/nota_empenho_siafi_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/programacao_financeira_siafi_ingest_dag.py b/airflow_lappis/dags/data_ingest/siafi/programacao_financeira_siafi_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/programacao_financeira_siafi_ingest_dag.py rename to airflow_lappis/dags/data_ingest/siafi/programacao_financeira_siafi_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/dados_afastamento_historico_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_afastamento_historico_siape_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/dados_afastamento_historico_siape_ingest_dag.py rename to airflow_lappis/dags/data_ingest/siape/dados_afastamento_historico_siape_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/dados_afastamento_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_afastamento_siape_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/dados_afastamento_siape_ingest_dag.py rename to airflow_lappis/dags/data_ingest/siape/dados_afastamento_siape_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/dados_curriculo_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_curriculo_siape_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/dados_curriculo_siape_ingest_dag.py rename to airflow_lappis/dags/data_ingest/siape/dados_curriculo_siape_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/dados_dependentes_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_dependentes_siape_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/dados_dependentes_siape_ingest_dag.py rename to airflow_lappis/dags/data_ingest/siape/dados_dependentes_siape_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/dados_escolares_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_escolares_siape_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/dados_escolares_siape_ingest_dag.py rename to airflow_lappis/dags/data_ingest/siape/dados_escolares_siape_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/dados_financeiros_siape_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_financeiros_siape_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/dados_financeiros_siape_dag.py rename to airflow_lappis/dags/data_ingest/siape/dados_financeiros_siape_dag.py diff --git a/airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_funcionais_siape_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/dados_funcionais_siape_ingest_dag.py rename to airflow_lappis/dags/data_ingest/siape/dados_funcionais_siape_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/dados_pa_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_pa_siape_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/dados_pa_siape_ingest_dag.py rename to airflow_lappis/dags/data_ingest/siape/dados_pa_siape_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/dados_pessoais_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_pessoais_siape_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/dados_pessoais_siape_ingest_dag.py rename to airflow_lappis/dags/data_ingest/siape/dados_pessoais_siape_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/dados_uorg_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_uorg_siape_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/dados_uorg_siape_ingest_dag.py rename to airflow_lappis/dags/data_ingest/siape/dados_uorg_siape_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/lista_aposentadoria_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/lista_aposentadoria_siape_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/lista_aposentadoria_siape_ingest_dag.py rename to airflow_lappis/dags/data_ingest/siape/lista_aposentadoria_siape_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/lista_servidores_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/lista_servidores_siape_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/lista_servidores_siape_ingest_dag.py rename to airflow_lappis/dags/data_ingest/siape/lista_servidores_siape_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/lista_uorgs_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/lista_uorgs_siape_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/lista_uorgs_siape_ingest_dag.py rename to airflow_lappis/dags/data_ingest/siape/lista_uorgs_siape_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/pensoes_instituidas_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/pensoes_instituidas_siape_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/pensoes_instituidas_siape_ingest_dag.py rename to airflow_lappis/dags/data_ingest/siape/pensoes_instituidas_siape_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/cargos_funcao_ingest_dag.py b/airflow_lappis/dags/data_ingest/siorg/cargos_funcao_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/cargos_funcao_ingest_dag.py rename to airflow_lappis/dags/data_ingest/siorg/cargos_funcao_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/estrutura_organizacional_cargos_ingest_dag.py b/airflow_lappis/dags/data_ingest/siorg/estrutura_organizacional_cargos_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/estrutura_organizacional_cargos_ingest_dag.py rename to airflow_lappis/dags/data_ingest/siorg/estrutura_organizacional_cargos_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py b/airflow_lappis/dags/data_ingest/siorg/unidade_organizacional_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/unidade_organizacional_ingest_dag.py rename to airflow_lappis/dags/data_ingest/siorg/unidade_organizacional_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_ingest_dag.py similarity index 99% rename from airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py rename to airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_ingest_dag.py index 6b585352..6141c3c6 100755 --- a/airflow_lappis/dags/data_ingest/empenhos_tesouro_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_ingest_dag.py @@ -56,7 +56,7 @@ tags=["email", "empenhos", "tesouro"], ) as dag: - def process_email_data(**context: Dict[str, Any]) -> Optional[str]: + def process_email_data(**context: Dict[str, Any]) -> Optional[Any]: creds = json.loads(Variable.get("email_credentials")) EMAIL = creds["email"] diff --git a/airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/estagios_tesouro_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/estagios_tesouro_ingest_dag.py rename to airflow_lappis/dags/data_ingest/tesouro_gerencial/estagios_tesouro_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/nc_tesouro_ingest.dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/nc_tesouro_ingest.dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/nc_tesouro_ingest.dag.py rename to airflow_lappis/dags/data_ingest/tesouro_gerencial/nc_tesouro_ingest.dag.py diff --git a/airflow_lappis/dags/data_ingest/pf_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/pf_tesouro_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/pf_tesouro_ingest_dag.py rename to airflow_lappis/dags/data_ingest/tesouro_gerencial/pf_tesouro_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/notas_de_credito_ingest_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/notas_de_credito_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/notas_de_credito_ingest_dag.py rename to airflow_lappis/dags/data_ingest/transfere_gov/notas_de_credito_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/plano_acao_ingest_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/plano_acao_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/plano_acao_ingest_dag.py rename to airflow_lappis/dags/data_ingest/transfere_gov/plano_acao_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/programa_beneficiario_ingest_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/programa_beneficiario_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/programa_beneficiario_ingest_dag.py rename to airflow_lappis/dags/data_ingest/transfere_gov/programa_beneficiario_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/programacao_financeira_ingest_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/programacao_financeira_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/programacao_financeira_ingest_dag.py rename to airflow_lappis/dags/data_ingest/transfere_gov/programacao_financeira_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/programas_ingest_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/programas_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/programas_ingest_dag.py rename to airflow_lappis/dags/data_ingest/transfere_gov/programas_ingest_dag.py From 3c659429ca2361faf466d8d65941458614ed65fc Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Wed, 6 Aug 2025 15:43:51 -0300 Subject: [PATCH 138/317] feat(siape): melhora logging por processamento de CPF e data de ingestao --- .../siape/dados_pessoais_siape_ingest_dag.py | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/siape/dados_pessoais_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_pessoais_siape_ingest_dag.py index bf30d99c..7a274ab3 100644 --- a/airflow_lappis/dags/data_ingest/siape/dados_pessoais_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/dados_pessoais_siape_ingest_dag.py @@ -76,11 +76,12 @@ def fetch_and_store_dados_pessoais() -> None: # --- Continua fluxo normal --- query = "SELECT DISTINCT cpf FROM siape.lista_servidores WHERE cpf IS NOT NULL" cpfs = [row[0] for row in db.execute_query(query)] - logging.info(f"Total de CPFs encontrados: {len(cpfs)}") + total_cpfs = len(cpfs) + logging.info(f"Total de CPFs encontrados: {total_cpfs}") - for cpf in cpfs: + for idx, cpf in enumerate(cpfs, 1): try: - logging.info(f"Processando CPF: {cpf}") + logging.info(f"Consultando CPF {cpf} [{idx}/{total_cpfs}]") context = { "siglaSistema": "PETRVS-IPEA", @@ -99,10 +100,13 @@ def fetch_and_store_dados_pessoais() -> None: logging.debug(f"Dados parseados para CPF {cpf}: {dados}") if not dados: - logging.warning(f"Nenhum dado pessoal encontrado para CPF {cpf}") + logging.warning( + f"Nenhum dado encontrado para CPF {cpf} [{idx}/{total_cpfs}]" + ) continue dados["cpf"] = cpf + dados["dt_ingest"] = datetime.now().isoformat() logging.info(f"Dados finais prontos para inserção: {dados}") db.alter_table( @@ -119,10 +123,15 @@ def fetch_and_store_dados_pessoais() -> None: schema="siape", ) - logging.info(f"Dado pessoal inserido com sucesso para CPF {cpf}") + logging.info( + f"Dado inserido com sucesso para CPF {cpf} [{idx}/{total_cpfs}]" + ) except Exception as e: - logging.error(f"Erro ao processar CPF {cpf}: {e}", exc_info=True) + logging.error( + f"Erro ao processar CPF {cpf} [{idx}/{total_cpfs}]: {e}", + exc_info=True, + ) continue fetch_and_store_dados_pessoais() From 10f971fd36ab6e776f2e3509987625ef5febb64b Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Mon, 11 Aug 2025 12:40:51 +0000 Subject: [PATCH 139/317] Feat/visao orcamentaria ingest dag --- .../visao_orcamentaria_ingest.py | 290 ++++++++++++++++++ requirements.txt | 174 +---------- 2 files changed, 296 insertions(+), 168 deletions(-) create mode 100644 airflow_lappis/dags/data_ingest/tesouro_gerencial/visao_orcamentaria_ingest.py diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/visao_orcamentaria_ingest.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/visao_orcamentaria_ingest.py new file mode 100644 index 00000000..a059d701 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/visao_orcamentaria_ingest.py @@ -0,0 +1,290 @@ +from typing import Dict, Any, Optional, List +from airflow import DAG +from airflow.operators.python import PythonOperator +from airflow.models import Variable +from datetime import datetime, timedelta +import logging +import json +import pandas as pd +import io +import re +import zipfile +from cliente_email import fetch_email_with_zip +from cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn + +# Configurações básicas da DAG +default_args = { + "owner": "Davi", + "depends_on_past": False, + "retries": 1, + "retry_delay": timedelta(minutes=5), +} + +# Mapeamento das colunas para visão orçamentária +COLUMN_MAPPING = { + 0: "unidade_orcamentaria", + 1: "unidade_orcamentaria_desc", + 2: "acao_governo", + 3: "acao_governo_desc", + 4: "programa_governo", + 5: "programa_governo_desc", + 6: "unidade_plano_orcamentario", + 7: "plano_orcamentario_1", + 8: "plano_orcamentario_2", + 9: "programa_plano_orcamentario", + 10: "acao_plano_orcamentario", + 11: "plano_orcamentario_6", + 12: "plano_orcamentario_desc", + 13: "elemento_despesa", + 14: "elemento_despesa_desc", + 15: "orgao_uge", + 16: "orgao_uge_desc", + 17: "uge_matriz_filial", + 18: "ug_executora", + 19: "ug_executora_desc", + 20: "projeto_inicial_loa", + 21: "dotacao_inicial", + 22: "dotacao_atualizada", + 23: "credito_disponivel", + 24: "despesas_empenhadas", + 25: "despesas_a_liquidar", + 26: "despesar_a_pagar", + 27: "despesas_pagas", + 28: "restos_a_pagar_inscritos", + 29: "restos_a_pagar_pagos", +} + +EMAIL_SUBJECT = "visao_orcamentaria_total_ipea" + +# Configurações da DAG +with DAG( + dag_id="visao_orcamentaria_ingest", + default_args=default_args, + description=( + "DAG processa anexos da visão orçamentária total IPEA " + "vindo do email, formata e insere no db" + ), + schedule_interval="0 13 * * 1-6", + start_date=datetime(2023, 12, 1), + catchup=False, + tags=["email", "visao_orcamentaria", "tesouro"], +) as dag: + + def _is_valid_data_line(line: str, columns: List[str]) -> bool: + """Verifica se a linha contém dados válidos para processamento.""" + # Verifica se é linha de cabeçalho, separador ou vazia + header_indicators = [ + "Páginas:", + "Unidade Orçamentária", + '"Unidade Orçamentária"', + '"UG Executora"', + '"PROJETO INICIAL DA LOA"', + ] + + if ( + not line + or line.startswith(" ") + or len(columns) < 20 + or any(indicator in line for indicator in header_indicators) + ): + return False + + # Verifica se tem dados essenciais preenchidos + unidade_orc = columns[0].strip('"').strip() if columns else "" + elemento_desp = columns[13].strip('"').strip() if len(columns) > 13 else "" + + return bool( + unidade_orc + and unidade_orc not in ["", '""'] + and elemento_desp + and elemento_desp not in ["", '""'] + ) + + def _process_data_block(data_lines: List[str], year: str) -> List[Dict[str, Any]]: + """Processa um bloco de dados de um ano específico.""" + block_data = [] + + for line in data_lines: + line = line.strip() + columns = line.split("\t") + + if not _is_valid_data_line(line, columns): + continue + + # Remove aspas e limpa colunas + columns = [col.strip('"').strip() for col in columns] + + # Cria registro com mapeamento de colunas + row_data = { + col_name: columns[col_index] if col_index < len(columns) else "" + for col_index, col_name in COLUMN_MAPPING.items() + } + row_data["ano_exercicio"] = year + block_data.append(row_data) + + return block_data + + def _parse_csv_by_year_blocks(csv_content: str) -> List[Dict[str, Any]]: + """ + Processa CSV organizando dados por blocos de ano. + + Lógica: + 1. Encontra linhas com "Ano Lançamento: XXXX" + 2. Pula as próximas 5 linhas (cabeçalhos) + 3. Processa os dados até encontrar o próximo bloco ou fim do arquivo + 4. Adiciona a coluna ano_exercicio com o ano extraído + """ + lines = csv_content.strip().split("\n") + processed_data = [] + current_year = None + data_start_index = None + + logging.info(f"Iniciando processamento do CSV com {len(lines)} linhas") + + for i, line in enumerate(lines): + year_match = re.search(r"Ano Lançamento:\s*(\d{4})", line) + + if year_match: + # Processa bloco anterior se existir + if current_year and data_start_index is not None: + data_lines = lines[ + data_start_index : i - 2 + ] # Deixa margem para linhas vazias + year_data = _process_data_block(data_lines, current_year) + logging.info( + f"Processados {len(year_data)} registros para o ano " + f"{current_year}" + ) + processed_data.extend(year_data) + + # Inicia novo bloco + current_year = year_match.group(1) + data_start_index = i + 6 # Pula 5 linhas após "Ano Lançamento:" + logging.info( + f"Iniciando processamento do ano {current_year} a partir da " + f"linha {data_start_index}" + ) + + # Processa o último bloco + if current_year and data_start_index is not None: + data_lines = lines[data_start_index:] + year_data = _process_data_block(data_lines, current_year) + logging.info( + f"Processados {len(year_data)} registros para o último ano " + f"{current_year}" + ) + processed_data.extend(year_data) + + logging.info( + f"Processamento concluído. Total de registros: {len(processed_data)}" + ) + return processed_data + + def process_email_data(**context: Dict[str, Any]) -> Optional[str]: + """Processa o email e retorna os dados formatados.""" + creds = json.loads(Variable.get("email_credentials")) + + try: + logging.info("Iniciando o processamento dos emails...") + + # Busca o email com attachments ZIP + zip_payload = fetch_email_with_zip( + creds["imap_server"], + creds["email"], + creds["password"], + creds["sender_email"], + EMAIL_SUBJECT, + ) + + if not zip_payload: + logging.warning("Nenhum e-mail encontrado com o assunto esperado.") + return None + + # Extrai o CSV do ZIP (UTF-16) + with zipfile.ZipFile(io.BytesIO(zip_payload)) as zip_file: + for file_name in zip_file.namelist(): + if file_name.endswith(".csv"): + raw_data = zip_file.read(file_name) + csv_content = raw_data.decode("utf-16") + break + else: + logging.warning("Nenhum arquivo CSV encontrado no ZIP.") + return None + + # Processa o CSV com a lógica de blocos por ano + processed_data = _parse_csv_by_year_blocks(csv_content) + + if not processed_data: + logging.warning("Nenhum dado foi processado do CSV.") + return None + + # Converte para CSV string + df = pd.DataFrame(processed_data) + csv_string = df.to_csv(index=False) + + logging.info( + f"CSV processado com sucesso. Dados encontrados: " + f"{len(processed_data)} registros" + ) + return csv_string + + except Exception as e: + logging.error(f"Erro no processamento dos emails: {str(e)}") + raise + + def insert_and_clean_data(**context: Dict[str, Any]) -> None: + """Insere os dados no banco e limpa duplicados.""" + try: + task_instance: Any = context["ti"] + csv_data: Any = task_instance.xcom_pull(task_ids="process_emails") + + if not csv_data: + logging.warning("Nenhum dado para inserir no banco.") + return + + df = pd.read_csv(io.StringIO(csv_data)) + data = df.to_dict(orient="records") + + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + # Insere os dados + db.insert_data( + data, + "visao_orcamentaria_total", + schema="siafi", + ) + + logging.info( + f"Dados inseridos com sucesso no banco de dados. Total: " + f"{len(data)} registros" + ) + + # Remove duplicados + column_mapping_with_year = {**COLUMN_MAPPING, 30: "ano_exercicio"} + db.remove_duplicates( + "visao_orcamentaria_total_ipea", column_mapping_with_year, schema="siafi" + ) + + logging.info("Limpeza de duplicados concluída com sucesso.") + + except Exception as e: + logging.error(f"Erro ao inserir dados ou limpar duplicados: {str(e)}") + raise + + # Definição das tarefas + process_emails_task = PythonOperator( + task_id="process_emails", + python_callable=process_email_data, + provide_context=True, + ) + + insert_and_clean_task = PythonOperator( + task_id="insert_and_clean", + python_callable=insert_and_clean_data, + provide_context=True, + ) + + # Fluxo da DAG + process_emails_task >> insert_and_clean_task diff --git a/requirements.txt b/requirements.txt index d525ddf1..291a0131 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,168 +1,6 @@ -aenum==3.1.15 ; python_version >= "3.11" and python_version < "3.12" -agate==1.9.1 ; python_version >= "3.11" and python_version < "3.12" -aiohappyeyeballs==2.6.1 ; python_version >= "3.11" and python_version < "3.12" -aiohttp==3.10.11 ; python_version >= "3.11" and python_version < "3.12" -aiosignal==1.3.2 ; python_version >= "3.11" and python_version < "3.12" -alembic==1.15.2 ; python_version >= "3.11" and python_version < "3.12" -annotated-types==0.7.0 ; python_version >= "3.11" and python_version < "3.12" -anyio==4.9.0 ; python_version >= "3.11" and python_version < "3.12" -apache-airflow-providers-common-io==1.4.2 ; python_version >= "3.11" and python_version < "3.12" -apache-airflow-providers-common-sql==1.20.0 ; python_version >= "3.11" and python_version < "3.12" -apache-airflow-providers-ftp==3.11.1 ; python_version >= "3.11" and python_version < "3.12" -apache-airflow-providers-http==4.13.3 ; python_version >= "3.11" and python_version < "3.12" -apache-airflow-providers-imap==3.7.0 ; python_version >= "3.11" and python_version < "3.12" -apache-airflow-providers-postgres==5.14.0 ; python_version >= "3.11" and python_version < "3.12" -apache-airflow-providers-sqlite==3.9.1 ; python_version >= "3.11" and python_version < "3.12" -apache-airflow==2.8.1 ; python_version >= "3.11" and python_version < "3.12" -apispec[yaml]==6.8.1 ; python_version >= "3.11" and python_version < "3.12" -argcomplete==3.6.1 ; python_version >= "3.11" and python_version < "3.12" -asgiref==3.8.1 ; python_version >= "3.11" and python_version < "3.12" -astronomer-cosmos==1.9.2 ; python_version >= "3.11" and python_version < "3.12" -attrs==25.3.0 ; python_version >= "3.11" and python_version < "3.12" -babel==2.17.0 ; python_version >= "3.11" and python_version < "3.12" -blinker==1.9.0 ; python_version >= "3.11" and python_version < "3.12" -cachelib==0.13.0 ; python_version >= "3.11" and python_version < "3.12" -certifi==2025.1.31 ; python_version >= "3.11" and python_version < "3.12" -cffi==1.17.1 ; python_version >= "3.11" and python_version < "3.12" and platform_python_implementation != "PyPy" -charset-normalizer==3.4.1 ; python_version >= "3.11" and python_version < "3.12" -click==8.1.8 ; python_version >= "3.11" and python_version < "3.12" -clickclick==20.10.2 ; python_version >= "3.11" and python_version < "3.12" -colorama==0.4.6 ; python_version >= "3.11" and python_version < "3.12" -colorlog==4.8.0 ; python_version >= "3.11" and python_version < "3.12" -configupdater==3.2 ; python_version >= "3.11" and python_version < "3.12" -connexion[flask]==2.14.1 ; python_version >= "3.11" and python_version < "3.12" -cron-descriptor==1.4.5 ; python_version >= "3.11" and python_version < "3.12" -croniter==6.0.0 ; python_version >= "3.11" and python_version < "3.12" -cryptography==44.0.2 ; python_version >= "3.11" and python_version < "3.12" -daff==1.3.46 ; python_version >= "3.11" and python_version < "3.12" -dbt-adapters==1.14.3 ; python_version >= "3.11" and python_version < "3.12" -dbt-common==1.16.0 ; python_version >= "3.11" and python_version < "3.12" -dbt-core==1.9.3 ; python_version >= "3.11" and python_version < "3.12" -dbt-extractor==0.5.1 ; python_version >= "3.11" and python_version < "3.12" -dbt-postgres==1.9.0 ; python_version >= "3.11" and python_version < "3.12" -dbt-semantic-interfaces==0.7.4 ; python_version >= "3.11" and python_version < "3.12" -deepdiff==7.0.1 ; python_version >= "3.11" and python_version < "3.12" -deprecated==1.2.18 ; python_version >= "3.11" and python_version < "3.12" -deprecation==2.1.0 ; python_version >= "3.11" and python_version < "3.12" -dill==0.3.9 ; python_version >= "3.11" and python_version < "3.12" -distlib==0.3.9 ; python_version >= "3.11" and python_version < "3.12" -dnspython==2.7.0 ; python_version >= "3.11" and python_version < "3.12" -email-validator==1.3.1 ; python_version >= "3.11" and python_version < "3.12" -filelock==3.18.0 ; python_version >= "3.11" and python_version < "3.12" -flask-appbuilder==4.3.10 ; python_version >= "3.11" and python_version < "3.12" -flask-babel==2.0.0 ; python_version >= "3.11" and python_version < "3.12" -flask-caching==2.3.1 ; python_version >= "3.11" and python_version < "3.12" -flask-jwt-extended==4.7.1 ; python_version >= "3.11" and python_version < "3.12" -flask-limiter==3.12 ; python_version >= "3.11" and python_version < "3.12" -flask-login==0.6.3 ; python_version >= "3.11" and python_version < "3.12" -flask-session==0.5.0 ; python_version >= "3.11" and python_version < "3.12" -flask-sqlalchemy==2.5.1 ; python_version >= "3.11" and python_version < "3.12" -flask-wtf==1.2.2 ; python_version >= "3.11" and python_version < "3.12" -flask==2.2.5 ; python_version >= "3.11" and python_version < "3.12" -frozenlist==1.5.0 ; python_version >= "3.11" and python_version < "3.12" -fsspec==2025.3.2 ; python_version >= "3.11" and python_version < "3.12" -google-re2==1.1.20240702 ; python_version >= "3.11" and python_version < "3.12" -googleapis-common-protos==1.69.2 ; python_version >= "3.11" and python_version < "3.12" -greenlet==3.1.1 ; python_version >= "3.11" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32") and python_version < "3.12" -grpcio==1.71.0 ; python_version >= "3.11" and python_version < "3.12" -gunicorn==23.0.0 ; python_version >= "3.11" and python_version < "3.12" -h11==0.14.0 ; python_version >= "3.11" and python_version < "3.12" -httpcore==1.0.7 ; python_version >= "3.11" and python_version < "3.12" -httpx==0.28.1 ; python_version >= "3.11" and python_version < "3.12" -idna==3.10 ; python_version >= "3.11" and python_version < "3.12" -imap-tools==1.10.0 ; python_version >= "3.11" and python_version < "3.12" -importlib-metadata==6.11.0 ; python_version >= "3.11" and python_version < "3.12" -inflection==0.5.1 ; python_version >= "3.11" and python_version < "3.12" -isodate==0.6.1 ; python_version >= "3.11" and python_version < "3.12" -itsdangerous==2.2.0 ; python_version >= "3.11" and python_version < "3.12" -jinja2==3.1.6 ; python_version >= "3.11" and python_version < "3.12" -jsonschema-specifications==2024.10.1 ; python_version >= "3.11" and python_version < "3.12" -jsonschema==4.23.0 ; python_version >= "3.11" and python_version < "3.12" -lazy-object-proxy==1.10.0 ; python_version >= "3.11" and python_version < "3.12" -leather==0.4.0 ; python_version >= "3.11" and python_version < "3.12" -limits==4.4.1 ; python_version >= "3.11" and python_version < "3.12" -linkify-it-py==2.0.3 ; python_version >= "3.11" and python_version < "3.12" -lockfile==0.12.2 ; python_version >= "3.11" and python_version < "3.12" -lxml==5.3.1 ; python_version >= "3.11" and python_version < "3.12" -mako==1.3.9 ; python_version >= "3.11" and python_version < "3.12" -markdown-it-py==3.0.0 ; python_version >= "3.11" and python_version < "3.12" -markdown==3.7 ; python_version >= "3.11" and python_version < "3.12" -markupsafe==3.0.2 ; python_version >= "3.11" and python_version < "3.12" -marshmallow-oneofschema==3.1.1 ; python_version >= "3.11" and python_version < "3.12" -marshmallow-sqlalchemy==0.26.1 ; python_version >= "3.11" and python_version < "3.12" -marshmallow==3.26.1 ; python_version >= "3.11" and python_version < "3.12" -mashumaro[msgpack]==3.14 ; python_version >= "3.11" and python_version < "3.12" -mdit-py-plugins==0.4.2 ; python_version >= "3.11" and python_version < "3.12" -mdurl==0.1.2 ; python_version >= "3.11" and python_version < "3.12" -more-itertools==10.6.0 ; python_version >= "3.11" and python_version < "3.12" -msgpack==1.1.0 ; python_version >= "3.11" and python_version < "3.12" -multidict==6.2.0 ; python_version >= "3.11" and python_version < "3.12" -networkx==3.4.2 ; python_version >= "3.11" and python_version < "3.12" -numpy==1.26.4 ; python_version >= "3.11" and python_version < "3.12" -opentelemetry-api==1.31.1 ; python_version >= "3.11" and python_version < "3.12" -opentelemetry-exporter-otlp-proto-common==1.31.1 ; python_version >= "3.11" and python_version < "3.12" -opentelemetry-exporter-otlp-proto-grpc==1.31.1 ; python_version >= "3.11" and python_version < "3.12" -opentelemetry-exporter-otlp-proto-http==1.31.1 ; python_version >= "3.11" and python_version < "3.12" -opentelemetry-exporter-otlp==1.31.1 ; python_version >= "3.11" and python_version < "3.12" -opentelemetry-proto==1.31.1 ; python_version >= "3.11" and python_version < "3.12" -opentelemetry-sdk==1.31.1 ; python_version >= "3.11" and python_version < "3.12" -opentelemetry-semantic-conventions==0.52b1 ; python_version >= "3.11" and python_version < "3.12" -ordered-set==4.1.0 ; python_version >= "3.11" and python_version < "3.12" -packaging==24.2 ; python_version >= "3.11" and python_version < "3.12" -pandas==2.2.3 ; python_version >= "3.11" and python_version < "3.12" -parsedatetime==2.6 ; python_version >= "3.11" and python_version < "3.12" -pathspec==0.12.1 ; python_version >= "3.11" and python_version < "3.12" -pendulum==3.0.0 ; python_version >= "3.11" and python_version < "3.12" -platformdirs==4.3.7 ; python_version >= "3.11" and python_version < "3.12" -pluggy==1.5.0 ; python_version >= "3.11" and python_version < "3.12" -prison==0.2.1 ; python_version >= "3.11" and python_version < "3.12" -propcache==0.3.1 ; python_version >= "3.11" and python_version < "3.12" -protobuf==5.29.4 ; python_version >= "3.11" and python_version < "3.12" -psutil==7.0.0 ; python_version >= "3.11" and python_version < "3.12" -psycopg2-binary==2.9.10 ; python_version >= "3.11" and python_version < "3.12" -pycparser==2.22 ; python_version >= "3.11" and python_version < "3.12" and platform_python_implementation != "PyPy" -pydantic-core==2.33.0 ; python_version >= "3.11" and python_version < "3.12" -pydantic==2.11.0 ; python_version >= "3.11" and python_version < "3.12" -pygments==2.19.1 ; python_version >= "3.11" and python_version < "3.12" -pyjwt==2.10.1 ; python_version >= "3.11" and python_version < "3.12" -python-daemon==3.1.2 ; python_version >= "3.11" and python_version < "3.12" -python-dateutil==2.9.0.post0 ; python_version >= "3.11" and python_version < "3.12" -python-nvd3==0.16.0 ; python_version >= "3.11" and python_version < "3.12" -python-slugify==8.0.4 ; python_version >= "3.11" and python_version < "3.12" -pytimeparse==1.1.8 ; python_version >= "3.11" and python_version < "3.12" -pytz==2025.2 ; python_version >= "3.11" and python_version < "3.12" -pyyaml==6.0.2 ; python_version >= "3.11" and python_version < "3.12" -referencing==0.36.2 ; python_version >= "3.11" and python_version < "3.12" -requests-file==2.1.0 ; python_version >= "3.11" and python_version < "3.12" -requests-toolbelt==1.0.0 ; python_version >= "3.11" and python_version < "3.12" -requests==2.32.3 ; python_version >= "3.11" and python_version < "3.12" -rfc3339-validator==0.1.4 ; python_version >= "3.11" and python_version < "3.12" -rich-argparse==1.7.0 ; python_version >= "3.11" and python_version < "3.12" -rich==13.9.4 ; python_version >= "3.11" and python_version < "3.12" -rpds-py==0.24.0 ; python_version >= "3.11" and python_version < "3.12" -setproctitle==1.3.5 ; python_version >= "3.11" and python_version < "3.12" -six==1.17.0 ; python_version >= "3.11" and python_version < "3.12" -sniffio==1.3.1 ; python_version >= "3.11" and python_version < "3.12" -snowplow-tracker==1.1.0 ; python_version >= "3.11" and python_version < "3.12" -sqlalchemy-jsonfield==1.0.2 ; python_version >= "3.11" and python_version < "3.12" -sqlalchemy-utils==0.41.2 ; python_version >= "3.11" and python_version < "3.12" -sqlalchemy==1.4.54 ; python_version >= "3.11" and python_version < "3.12" -sqlparse==0.5.3 ; python_version >= "3.11" and python_version < "3.12" -tabulate==0.9.0 ; python_version >= "3.11" and python_version < "3.12" -tenacity==9.0.0 ; python_version >= "3.11" and python_version < "3.12" -termcolor==3.0.0 ; python_version >= "3.11" and python_version < "3.12" -text-unidecode==1.3 ; python_version >= "3.11" and python_version < "3.12" -typing-extensions==4.13.0 ; python_version >= "3.11" and python_version < "3.12" -typing-inspection==0.4.0 ; python_version >= "3.11" and python_version < "3.12" -tzdata==2025.2 ; python_version >= "3.11" and python_version < "3.12" -uc-micro-py==1.0.3 ; python_version >= "3.11" and python_version < "3.12" -unicodecsv==0.14.1 ; python_version >= "3.11" and python_version < "3.12" -universal-pathlib==0.2.6 ; python_version >= "3.11" and python_version < "3.12" -urllib3==2.3.0 ; python_version >= "3.11" and python_version < "3.12" -virtualenv==20.30.0 ; python_version >= "3.11" and python_version < "3.12" -werkzeug==2.3.8 ; python_version >= "3.11" and python_version < "3.12" -wrapt==1.17.2 ; python_version >= "3.11" and python_version < "3.12" -wtforms==3.2.1 ; python_version >= "3.11" and python_version < "3.12" -yarl==1.18.3 ; python_version >= "3.11" and python_version < "3.12" -zeep==4.3.1 ; python_version >= "3.11" and python_version < "3.12" -zipp==3.21.0 ; python_version >= "3.11" and python_version < "3.12" +collate-sqllineage==1.6.0 +sqlparse==0.5 +astronomer-cosmos==1.9.0 +dbt-postgres==1.7.13 +imap-tools==1.10.0 +zeep==4.3.1 \ No newline at end of file From d70c58f60ef7f5f1776e87ebfb4ce23084369bfe Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Tue, 12 Aug 2025 00:54:41 -0300 Subject: [PATCH 140/317] =?UTF-8?q?feat(dt=5Fingest):=20adiciona=20data=20?= =?UTF-8?q?de=20ingest=C3=A3o=20e=20atualiza=C3=A7=C3=A3o=20para=20todas?= =?UTF-8?q?=20as=20ingest=C3=B5es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../compras_gov/contratos_inativos_ingest_dag.py | 4 ++++ .../data_ingest/compras_gov/contratos_ingest_dag.py | 4 ++++ .../compras_gov/cronograma_ingest_dag.py | 4 ++++ .../data_ingest/compras_gov/empenhos_ingest_dag.py | 1 + .../data_ingest/compras_gov/faturas_ingest_dag.py | 5 +++++ .../compras_gov/terceirizados_ingest_dag.py | 5 +++++ .../siafi/nota_credito_siafi_ingest_dag.py | 1 + .../siafi/nota_empenho_siafi_ingest_dag.py | 1 + .../programacao_financeira_siafi_ingest_dag.py | 1 + .../dados_afastamento_historico_siape_ingest_dag.py | 1 + .../siape/dados_afastamento_siape_ingest_dag.py | 1 + .../siape/dados_curriculo_siape_ingest_dag.py | 2 ++ .../siape/dados_dependentes_siape_ingest_dag.py | 1 + .../siape/dados_escolares_siape_ingest_dag.py | 1 + .../siape/dados_financeiros_siape_dag.py | 1 + .../data_ingest/siape/dados_pa_siape_ingest_dag.py | 1 + .../siape/dados_uorg_siape_ingest_dag.py | 1 + .../siape/lista_aposentadoria_siape_ingest_dag.py | 2 ++ .../siape/lista_servidores_siape_ingest_dag.py | 1 + .../siape/lista_uorgs_siape_ingest_dag.py | 4 ++++ .../siape/pensoes_instituidas_siape_ingest_dag.py | 4 ++++ .../data_ingest/siorg/cargos_funcao_ingest_dag.py | 1 + .../estrutura_organizacional_cargos_ingest_dag.py | 1 + .../siorg/unidade_organizacional_ingest_dag.py | 4 ++++ .../empenhos_tesouro_ingest_dag.py | 4 ++++ .../estagios_tesouro_ingest_dag.py | 13 ++++++++++++- .../tesouro_gerencial/nc_tesouro_ingest.dag.py | 4 ++++ .../tesouro_gerencial/pf_tesouro_ingest_dag.py | 4 ++++ .../tesouro_gerencial/visao_orcamentaria_ingest.py | 4 ++++ .../transfere_gov/notas_de_credito_ingest_dag.py | 4 ++++ .../transfere_gov/plano_acao_ingest_dag.py | 3 +++ .../programacao_financeira_ingest_dag.py | 4 ++++ .../transfere_gov/programas_ingest_dag.py | 2 ++ 33 files changed, 93 insertions(+), 1 deletion(-) diff --git a/airflow_lappis/dags/data_ingest/compras_gov/contratos_inativos_ingest_dag.py b/airflow_lappis/dags/data_ingest/compras_gov/contratos_inativos_ingest_dag.py index f0452797..482fd29c 100755 --- a/airflow_lappis/dags/data_ingest/compras_gov/contratos_inativos_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/compras_gov/contratos_inativos_ingest_dag.py @@ -48,6 +48,10 @@ def fetch_and_store_contratos_inativos() -> None: logging.info(f"Buscando contratos inativos para UG: {ug_code}") contratos = api.get_contratos_inativos_by_ug(ug_code) if contratos: + # Adicionar dt_ingest a cada contrato + for contrato in contratos: + contrato["dt_ingest"] = datetime.now().isoformat() + logging.info( f"Inserindo contratos inativos da UG {ug_code} no schema compras_gov" ) diff --git a/airflow_lappis/dags/data_ingest/compras_gov/contratos_ingest_dag.py b/airflow_lappis/dags/data_ingest/compras_gov/contratos_ingest_dag.py index 00f25e90..4cce191c 100755 --- a/airflow_lappis/dags/data_ingest/compras_gov/contratos_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/compras_gov/contratos_ingest_dag.py @@ -50,6 +50,10 @@ def fetch_and_store_contratos() -> None: contratos = api.get_contratos_by_ug(ug_code) if contratos: + # Adicionar dt_ingest a cada contrato + for contrato in contratos: + contrato["dt_ingest"] = datetime.now().isoformat() + logging.info(f"Inserindo contratos da UG {ug_code} no schema compras_gov") db.insert_data( contratos, diff --git a/airflow_lappis/dags/data_ingest/compras_gov/cronograma_ingest_dag.py b/airflow_lappis/dags/data_ingest/compras_gov/cronograma_ingest_dag.py index c6d264f9..aa623a20 100755 --- a/airflow_lappis/dags/data_ingest/compras_gov/cronograma_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/compras_gov/cronograma_ingest_dag.py @@ -39,6 +39,10 @@ def fetch_cronogramas() -> None: ) cronograma = api.get_cronograma_by_contrato_id(contrato_id) if cronograma: + # Adicionar dt_ingest a cada item do cronograma + for item in cronograma: + item["dt_ingest"] = datetime.now().isoformat() + logging.info( f"[cronograma_ingest_dag.py] Inserting cronograma for contrato ID: " f"{contrato_id} into PostgreSQL" diff --git a/airflow_lappis/dags/data_ingest/compras_gov/empenhos_ingest_dag.py b/airflow_lappis/dags/data_ingest/compras_gov/empenhos_ingest_dag.py index 0fd1f0af..179dd261 100755 --- a/airflow_lappis/dags/data_ingest/compras_gov/empenhos_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/compras_gov/empenhos_ingest_dag.py @@ -39,6 +39,7 @@ def fetch_empenhos() -> None: if empenhos: for empenho in empenhos: empenho["contrato_id"] = contrato_id + empenho["dt_ingest"] = datetime.now().isoformat() logging.info( f"[empenhos_ingest_dag.py] Inserting empenhos for contrato ID: " diff --git a/airflow_lappis/dags/data_ingest/compras_gov/faturas_ingest_dag.py b/airflow_lappis/dags/data_ingest/compras_gov/faturas_ingest_dag.py index 5c8d8d91..5529fc2b 100755 --- a/airflow_lappis/dags/data_ingest/compras_gov/faturas_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/compras_gov/faturas_ingest_dag.py @@ -35,6 +35,11 @@ def fetch_faturas() -> None: ) faturas = api.get_faturas_by_contrato_id(str(contrato_id)) + # Adicionar dt_ingest a cada fatura + if faturas: + for fatura in faturas: + fatura["dt_ingest"] = datetime.now().isoformat() + logging.info( f"[faturas_ingest_dag.py] Inserting faturas for contrato ID: " f"{contrato_id} into PostgreSQL" diff --git a/airflow_lappis/dags/data_ingest/compras_gov/terceirizados_ingest_dag.py b/airflow_lappis/dags/data_ingest/compras_gov/terceirizados_ingest_dag.py index 0475fb28..ed8d2512 100644 --- a/airflow_lappis/dags/data_ingest/compras_gov/terceirizados_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/compras_gov/terceirizados_ingest_dag.py @@ -33,6 +33,11 @@ def fetch_terceirizados() -> None: logging.info(f"Fetching terceirizados for contrato ID: " f"{contrato_id}") terceirizados = api.get_terceirizados_by_contrato_id(str(contrato_id)) + # Adicionar dt_ingest a cada terceirizado + if terceirizados: + for terceirizado in terceirizados: + terceirizado["dt_ingest"] = datetime.now().isoformat() + logging.info( f"Inserting terceirizados for contrato ID: " f"{contrato_id} into PostgreSQL" diff --git a/airflow_lappis/dags/data_ingest/siafi/nota_credito_siafi_ingest_dag.py b/airflow_lappis/dags/data_ingest/siafi/nota_credito_siafi_ingest_dag.py index ba5408b4..098bef97 100644 --- a/airflow_lappis/dags/data_ingest/siafi/nota_credito_siafi_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siafi/nota_credito_siafi_ingest_dag.py @@ -43,6 +43,7 @@ def fetch_and_store_nota_credito() -> None: ) if response: response["ano"] = ano + response["dt_ingest"] = datetime.now().isoformat() db.insert_data( [response], "nota_credito", diff --git a/airflow_lappis/dags/data_ingest/siafi/nota_empenho_siafi_ingest_dag.py b/airflow_lappis/dags/data_ingest/siafi/nota_empenho_siafi_ingest_dag.py index a929976b..77ff5cad 100644 --- a/airflow_lappis/dags/data_ingest/siafi/nota_empenho_siafi_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siafi/nota_empenho_siafi_ingest_dag.py @@ -76,6 +76,7 @@ def fetch_and_store_notas_empenho(**context: Dict[str, Any]) -> None: resultado = cliente.consultar_nota_empenho(ug, ano, num_empenho_str) if not resultado: break + resultado["dt_ingest"] = datetime.now().isoformat() db.insert_data( [resultado], "notas_empenho", diff --git a/airflow_lappis/dags/data_ingest/siafi/programacao_financeira_siafi_ingest_dag.py b/airflow_lappis/dags/data_ingest/siafi/programacao_financeira_siafi_ingest_dag.py index df6d0f1e..2749884a 100644 --- a/airflow_lappis/dags/data_ingest/siafi/programacao_financeira_siafi_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siafi/programacao_financeira_siafi_ingest_dag.py @@ -34,6 +34,7 @@ def fetch_and_store_programacao_financeira() -> None: ug_emitente, ano, num_lista ) if response: + response["dt_ingest"] = datetime.now().isoformat() db.insert_data( [response], "programacao_financeira_siafi", diff --git a/airflow_lappis/dags/data_ingest/siape/dados_afastamento_historico_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_afastamento_historico_siape_ingest_dag.py index 7d728b3a..a9fa1e12 100644 --- a/airflow_lappis/dags/data_ingest/siape/dados_afastamento_historico_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/dados_afastamento_historico_siape_ingest_dag.py @@ -63,6 +63,7 @@ def fetch_and_store_afastamento_historico() -> None: for row in dados: row["cpf"] = cpf + row["dt_ingest"] = datetime.now().isoformat() if dados: db.alter_table( diff --git a/airflow_lappis/dags/data_ingest/siape/dados_afastamento_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_afastamento_siape_ingest_dag.py index 1520bbc5..3c91dbb2 100644 --- a/airflow_lappis/dags/data_ingest/siape/dados_afastamento_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/dados_afastamento_siape_ingest_dag.py @@ -57,6 +57,7 @@ def fetch_and_store_dados_afastamento() -> None: continue dados["cpf"] = cpf + dados["dt_ingest"] = datetime.now().isoformat() if dados: db.alter_table( diff --git a/airflow_lappis/dags/data_ingest/siape/dados_curriculo_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_curriculo_siape_ingest_dag.py index 1b47751b..f2713ea3 100644 --- a/airflow_lappis/dags/data_ingest/siape/dados_curriculo_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/dados_curriculo_siape_ingest_dag.py @@ -56,6 +56,8 @@ def fetch_and_store_dados_curriculo() -> None: logging.warning(f"Nenhum dado de currículo encontrado para CPF {cpf}") continue + dados["dt_ingest"] = datetime.now().isoformat() + db.alter_table( data=dados, table_name="dados_curriculo", diff --git a/airflow_lappis/dags/data_ingest/siape/dados_dependentes_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_dependentes_siape_ingest_dag.py index e6ea0668..1abbf1ca 100644 --- a/airflow_lappis/dags/data_ingest/siape/dados_dependentes_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/dados_dependentes_siape_ingest_dag.py @@ -58,6 +58,7 @@ def fetch_and_store_dados_dependentes() -> None: for row in dados: row["cpf"] = cpf + row["dt_ingest"] = datetime.now().isoformat() db.alter_table( data=dados[0], diff --git a/airflow_lappis/dags/data_ingest/siape/dados_escolares_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_escolares_siape_ingest_dag.py index 760be974..5ed13643 100644 --- a/airflow_lappis/dags/data_ingest/siape/dados_escolares_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/dados_escolares_siape_ingest_dag.py @@ -57,6 +57,7 @@ def fetch_and_store_dados_escolares() -> None: continue dados["cpf"] = cpf + dados["dt_ingest"] = datetime.now().isoformat() db.alter_table( data=dados, diff --git a/airflow_lappis/dags/data_ingest/siape/dados_financeiros_siape_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_financeiros_siape_dag.py index 69bf521e..fd38f5c6 100644 --- a/airflow_lappis/dags/data_ingest/siape/dados_financeiros_siape_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/dados_financeiros_siape_dag.py @@ -57,6 +57,7 @@ def fetch_and_store_dados_financeiros() -> None: continue dados["cpf"] = cpf + dados["dt_ingest"] = datetime.now().isoformat() db.alter_table( data=dados, diff --git a/airflow_lappis/dags/data_ingest/siape/dados_pa_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_pa_siape_ingest_dag.py index f78f7100..3108cf45 100644 --- a/airflow_lappis/dags/data_ingest/siape/dados_pa_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/dados_pa_siape_ingest_dag.py @@ -92,6 +92,7 @@ def fetch_and_store_dados_pa() -> None: continue dados["cpf_servidor"] = cpf + dados["dt_ingest"] = datetime.now().isoformat() db.alter_table( data=dados, diff --git a/airflow_lappis/dags/data_ingest/siape/dados_uorg_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_uorg_siape_ingest_dag.py index 79d39f2d..14500e64 100644 --- a/airflow_lappis/dags/data_ingest/siape/dados_uorg_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/dados_uorg_siape_ingest_dag.py @@ -55,6 +55,7 @@ def fetch_and_store_dados_uorg() -> None: continue dados["cpf"] = cpf + dados["dt_ingest"] = datetime.now().isoformat() db.alter_table( data=dados, diff --git a/airflow_lappis/dags/data_ingest/siape/lista_aposentadoria_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/lista_aposentadoria_siape_ingest_dag.py index a6d37eb5..96d4bb6b 100644 --- a/airflow_lappis/dags/data_ingest/siape/lista_aposentadoria_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/lista_aposentadoria_siape_ingest_dag.py @@ -60,6 +60,8 @@ def fetch_and_store_aposentadoria_info() -> None: logging.warning(f"Nenhum dado encontrado para CPF {cpf}") continue + dados["dt_ingest"] = datetime.now().isoformat() + db.alter_table( data=dados, table_name="info_aposentadoria", diff --git a/airflow_lappis/dags/data_ingest/siape/lista_servidores_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/lista_servidores_siape_ingest_dag.py index 92339fbc..9476c0e0 100644 --- a/airflow_lappis/dags/data_ingest/siape/lista_servidores_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/lista_servidores_siape_ingest_dag.py @@ -59,6 +59,7 @@ def fetch_and_store_lista_servidores() -> None: for row in dados: row["codUorg"] = str(cod) + row["dt_ingest"] = datetime.now().isoformat() db.insert_data( dados, diff --git a/airflow_lappis/dags/data_ingest/siape/lista_uorgs_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/lista_uorgs_siape_ingest_dag.py index b3bd7a49..0c1cc67a 100644 --- a/airflow_lappis/dags/data_ingest/siape/lista_uorgs_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/lista_uorgs_siape_ingest_dag.py @@ -55,6 +55,10 @@ def fetch_and_store_lista_uorgs() -> None: logging.warning("Nenhum dado retornado da API listaUorgs") return + # Adicionar dt_ingest a cada registro + for registro in dados_lista: + registro["dt_ingest"] = datetime.now().isoformat() + for item in dados_lista: if "dataUltimaTransacao" in item: valor_bruto = item.pop("dataUltimaTransacao") diff --git a/airflow_lappis/dags/data_ingest/siape/pensoes_instituidas_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/pensoes_instituidas_siape_ingest_dag.py index 6a2c2fa3..ca9ae310 100644 --- a/airflow_lappis/dags/data_ingest/siape/pensoes_instituidas_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/pensoes_instituidas_siape_ingest_dag.py @@ -68,6 +68,10 @@ def fetch_and_store_pensoes_instituidas() -> None: registro["id_registro"] = identificador if dados: + # Adicionar dt_ingest a cada registro + for registro in dados: + registro["dt_ingest"] = datetime.now().isoformat() + # Usa o primeiro registro para criar/ajustar a estrutura da tabela db.alter_table( data=dados[0], diff --git a/airflow_lappis/dags/data_ingest/siorg/cargos_funcao_ingest_dag.py b/airflow_lappis/dags/data_ingest/siorg/cargos_funcao_ingest_dag.py index 1f72f873..27da9f22 100644 --- a/airflow_lappis/dags/data_ingest/siorg/cargos_funcao_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siorg/cargos_funcao_ingest_dag.py @@ -34,6 +34,7 @@ def fetch_and_store_cargos_funcao() -> None: if "cargosFuncoes" in tipo and "cargoFuncao" in tipo["cargosFuncoes"]: for cargo in tipo["cargosFuncoes"]["cargoFuncao"]: registro = {**tipo_base, **cargo} + registro["dt_ingest"] = datetime.now().isoformat() registros.append(registro) if registros: db.insert_data( diff --git a/airflow_lappis/dags/data_ingest/siorg/estrutura_organizacional_cargos_ingest_dag.py b/airflow_lappis/dags/data_ingest/siorg/estrutura_organizacional_cargos_ingest_dag.py index f53309a9..05ceed40 100644 --- a/airflow_lappis/dags/data_ingest/siorg/estrutura_organizacional_cargos_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siorg/estrutura_organizacional_cargos_ingest_dag.py @@ -38,6 +38,7 @@ def fetch_estrutura_organizacional_cargos() -> None: if estrutura_cargos: estrutura_cargos["ordem_grandeza"] = ordem_grandeza + estrutura_cargos["dt_ingest"] = datetime.now().isoformat() db.insert_data( [estrutura_cargos], diff --git a/airflow_lappis/dags/data_ingest/siorg/unidade_organizacional_ingest_dag.py b/airflow_lappis/dags/data_ingest/siorg/unidade_organizacional_ingest_dag.py index 3022c91c..80f64097 100755 --- a/airflow_lappis/dags/data_ingest/siorg/unidade_organizacional_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siorg/unidade_organizacional_ingest_dag.py @@ -46,6 +46,10 @@ def fetch_estrutura_organizacional_resumida() -> None: codigo_unidade=codigo_unidade, ) if estrutura_resumida: + # Adicionar dt_ingest a cada item + for item in estrutura_resumida: + item["dt_ingest"] = datetime.now().isoformat() + logging.info( "[unidade_organizacional_ingest_dag.py] " "Inserting estrutura organizacional resumida for " diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_ingest_dag.py index 6141c3c6..3ebeedf1 100755 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_ingest_dag.py @@ -104,6 +104,10 @@ def insert_data_to_db(**context: Dict[str, Any]) -> None: df = df[df["ne_ccor_ano_emissao"].astype(str).str.startswith("20")] data = df.to_dict(orient="records") + # Adicionar dt_ingest a cada registro + for record in data: + record["dt_ingest"] = datetime.now().isoformat() + postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/estagios_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/estagios_tesouro_ingest_dag.py index 1a068f96..f3972327 100755 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/estagios_tesouro_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/estagios_tesouro_ingest_dag.py @@ -102,7 +102,18 @@ def insert_data_to_db(**context: Dict[str, Any]) -> None: postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) - db.insert_csv_data(csv_data, "estagios_tesouro", schema="siafi") + # Adicionar dt_ingest aos dados antes da inserção + # Se csv_data for uma string, precisamos convertê-la para uma lista de dicts + if isinstance(csv_data, str): + import ast + + csv_data = ast.literal_eval(csv_data) + + # Adicionar dt_ingest a cada registro + for record in csv_data: + record["dt_ingest"] = datetime.now().isoformat() + + db.insert_data(csv_data, "estagios_tesouro", schema="siafi") logging.info("Dados inseridos com sucesso no banco de dados.") except Exception as e: logging.error("Erro ao inserir dados no banco: %s", str(e)) diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/nc_tesouro_ingest.dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/nc_tesouro_ingest.dag.py index 0ef6ef64..dea14f13 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/nc_tesouro_ingest.dag.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/nc_tesouro_ingest.dag.py @@ -157,6 +157,10 @@ def combine_data(**context: Dict[str, Any]) -> List[Dict]: combined_data = enviadas_data + recebidas_data + # Adicionar dt_ingest a cada registro + for record in combined_data: + record["dt_ingest"] = datetime.now().isoformat() + logging.info(f"Dados combinados: {len(combined_data)} registros no total.") return combined_data except Exception as e: diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/pf_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/pf_tesouro_ingest_dag.py index 11ebe4e8..95c4e99e 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/pf_tesouro_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/pf_tesouro_ingest_dag.py @@ -186,6 +186,10 @@ def insert_data_to_db(**context: Dict[str, Any]) -> None: df = pd.read_csv(io.StringIO(combined_data)) data = df.to_dict(orient="records") + # Adicionar dt_ingest a cada registro + for record in data: + record["dt_ingest"] = datetime.now().isoformat() + postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/visao_orcamentaria_ingest.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/visao_orcamentaria_ingest.py index a059d701..898679a0 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/visao_orcamentaria_ingest.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/visao_orcamentaria_ingest.py @@ -221,6 +221,10 @@ def process_email_data(**context: Dict[str, Any]) -> Optional[str]: # Converte para CSV string df = pd.DataFrame(processed_data) + + # Adicionar dt_ingest a cada registro + df["dt_ingest"] = datetime.now().isoformat() + csv_string = df.to_csv(index=False) logging.info( diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/notas_de_credito_ingest_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/notas_de_credito_ingest_dag.py index 7baabab5..e47e68d8 100644 --- a/airflow_lappis/dags/data_ingest/transfere_gov/notas_de_credito_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transfere_gov/notas_de_credito_ingest_dag.py @@ -45,6 +45,10 @@ def fetch_and_store_notas_de_credito() -> None: for ug_code in ug_codes: notas_de_credito = api.get_notas_de_credito_by_ug(ug_code) if notas_de_credito: + # Adicionar dt_ingest a cada nota + for nota in notas_de_credito: + nota["dt_ingest"] = datetime.now().isoformat() + db.insert_data( notas_de_credito, "notas_de_credito", diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/plano_acao_ingest_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/plano_acao_ingest_dag.py index a9674fc7..a3c858e1 100644 --- a/airflow_lappis/dags/data_ingest/transfere_gov/plano_acao_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transfere_gov/plano_acao_ingest_dag.py @@ -31,6 +31,9 @@ def fetch_and_store_planos_acao() -> None: for id_programa in id_programas: planos_acao_data = api.get_planos_acao_by_id_programa(id_programa) if planos_acao_data: + # Adicionar dt_ingest a cada plano + for plano in planos_acao_data: + plano["dt_ingest"] = datetime.now().isoformat() db.insert_data( planos_acao_data, diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/programacao_financeira_ingest_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/programacao_financeira_ingest_dag.py index 52b40dcb..22887bfe 100644 --- a/airflow_lappis/dags/data_ingest/transfere_gov/programacao_financeira_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transfere_gov/programacao_financeira_ingest_dag.py @@ -45,6 +45,10 @@ def fetch_and_store_programacao_financeira() -> None: for ug_code in ug_codes: programacao_financeira = api.get_programacao_financeira_by_ug(ug_code) if programacao_financeira: + # Adicionar dt_ingest a cada item + for item in programacao_financeira: + item["dt_ingest"] = datetime.now().isoformat() + db.insert_data( programacao_financeira, "programacao_financeira", diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/programas_ingest_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/programas_ingest_dag.py index 3783cce2..3a44235e 100644 --- a/airflow_lappis/dags/data_ingest/transfere_gov/programas_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transfere_gov/programas_ingest_dag.py @@ -33,6 +33,7 @@ def fetch_and_update_programas() -> None: programas_data = api.get_programa_by_id_programa(id_programa) if programas_data and len(programas_data) > 0: programa = programas_data[0] + programa["dt_ingest"] = datetime.now().isoformat() # Alter table to add any new columns needed db.alter_table(programa, "programas", schema="transfere_gov") @@ -64,6 +65,7 @@ def fetch_and_update_programas_by_sigla() -> None: programas_data = api.get_programas_by_sigla_unidade_descentralizadora(sigla) if programas_data and len(programas_data) > 0: for programa in programas_data: + programa["dt_ingest"] = datetime.now().isoformat() db.alter_table(programa, "programas", schema="transfere_gov") db.insert_data( programas_data, From 46b79c89df8d268aae9a3a86e13fbc6db53046ef Mon Sep 17 00:00:00 2001 From: Mateus de Castro Date: Wed, 13 Aug 2025 15:25:01 +0000 Subject: [PATCH 141/317] quantitativo alocados e ocupados e duplicatas de afastamento consolidado --- .../silver/afastamento_consolidado.sql | 60 ++++++- .../silver/tabela_correlacao_cargos.sql | 147 +++++++++++++++--- .../dags/dbt/ipea/models/schema.yml | 2 +- 3 files changed, 183 insertions(+), 26 deletions(-) diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/afastamento_consolidado.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/afastamento_consolidado.sql index 04ae52dd..d04b306f 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/afastamento_consolidado.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/afastamento_consolidado.sql @@ -54,8 +54,64 @@ with from {{ ref("afastamento_historico") }} ), - nomes_dt as (select distinct nome_pessoa, cpf from {{ ref("dados_pessoais") }}) + nomes_dt as (select distinct nome_pessoa, cpf from {{ ref("dados_pessoais") }}), + + funcoes_chefia as ( + select distinct cpf, cod_funcao, sigla_uorg_exercicio + from {{ ref("dados_funcionais") }} + ), + + -- Retirando duplicatas entre afastamento_historico e dados_afastamento + grupamentos as ( + select *, rank() over (partition by cpf order by dt_ini) as ordenacao + from dados_afastamento_totais + ), + + prioridades as ( + select + *, + row_number() over ( + partition by cpf, ordenacao + order by + case + when origem_dados = 'dados_afastamento' + then 1 + when origem_dados = 'afastamento_historico' + then 2 + end + ) as prioridade + from grupamentos + ), + + resultado as ( + select + adiantamento_salario_ferias, + ano_exercicio, + dt_fim, + dt_fim_aquisicao, + dt_ini, + dt_inicio_aquisicao, + dt_inicio_ferias_interrompidas, + dias_restantes, + gratificacao_natalina, + numero_parcela, + parcela_continuacao_interrupcao, + parcela_interrompida, + qtde_dias, + cpf, + cod_diploma_afastamento, + cod_ocorrencia, + dt_publicacao_afastamento, + desc_diploma_afastamento, + desc_ocorrencia, + numero_diploma_afastamento, + gr_matricula, + origem_dados + from prioridades + where prioridade = 1 + ) select * -from dados_afastamento_totais +from resultado left join nomes_dt using (cpf) +left join funcoes_chefia using (cpf) diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/tabela_correlacao_cargos.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/tabela_correlacao_cargos.sql index b9ca9eaf..907cbd0b 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/tabela_correlacao_cargos.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/tabela_correlacao_cargos.sql @@ -1,36 +1,99 @@ with + + correcao_funcao as ( + select *, replace(funcao, ' ', '') as funcao_sigla + from {{ ref("estrutura_organizacional_cargos") }} + ), + codigos_siorg as ( select distinct - replace(funcao, ' ', '') as funcao, - nomeunidade, + funcao_sigla, + eorg.nomeunidade, + eorg.codigounidade, + eorg.ordem_grandeza, + eorg.denominacao, + eorg.codigo_instancia, + uo.codigounidadepai, + uo.caminho_unidade, case - when siglaunidade = 'GABIN-IPEA' then 'GABIN' else siglaunidade + when eorg.siglaunidade = 'GABIN-IPEA' then 'GABIN' else siglaunidade end as siglaunidade, - denominacao - from {{ ref("estrutura_organizacional_cargos") }} + substring(funcao_sigla, length(funcao_sigla) - 2, 1) as categoria_cargo, + -- hierarquia do cargo está sendo definida a partir da fórmula: + -- (categoria do cargo * 1000) - nível do cargo + -- quanto menor a hierarquia, maior o cargo + right(funcao_sigla, 2) as nivel_cargo, + cast(substring(funcao_sigla, length(funcao_sigla) - 2, 1) as int) * 1000 + - cast(right(funcao, 2) as int) as hierarquia_cargo + from correcao_funcao as eorg + inner join + {{ ref("unidade_organizacional") }} as uo + on eorg.codigounidade = uo.codigounidade ), codigos_siape as ( select distinct - cod_funcao, - nome_uorg_exercicio, - sigla_uorg_exercicio, - nome_cargo, - matricula_siape, - substring(cod_funcao, 1, 1) - || substring(cod_funcao, length(cod_funcao) - 2, 3) as codigo_combinacao_siape - from {{ ref("dados_funcionais") }} - where cod_funcao is not null and dt_ocorr_aposentadoria is null + df.cod_funcao, + df.nome_uorg_exercicio, + df.sigla_uorg_exercicio, + df.nome_cargo, + df.matricula_siape, + df.cpf, + df.cpf_chefia_imediata, + df.cod_situacao_funcional, + df.nome_situacao_funcional, + dp.nome_pessoa, + dp.dt_nascimento, + dp.nome_sexo, + dp.nome_estado_civil, + dp.nome_nacionalidade, + dp.nome_cor, + dp.uf_nascimento, + dp.nome_municipio_nascimento, + uo.codigounidade as codigounidade_alternativa, + uo.caminho_unidade as caminho_unidade_alternativa, + uo.codigounidadepai as codigounidadepai_alternativa, + uo.ordem_grandeza as ordem_grandeza_alternativa, + substring(df.cod_funcao, 1, 1) || substring( + df.cod_funcao, length(df.cod_funcao) - 2, 3 + ) as codigo_combinacao_siape + from {{ ref("dados_funcionais") }} as df + left join {{ ref("dados_pessoais") }} as dp on df.cpf = dp.cpf + left join + {{ ref("unidade_organizacional") }} as uo + on df.sigla_uorg_exercicio = uo.sigla + where dt_ocorr_aposentadoria is null and cod_funcao is not null ), + -- select count(*) from codigos_siape; codigo_siorg_combinado as ( select *, - substring(funcao, 1, 1) - || substring(funcao, length(funcao) - 2, 3) as codigo_combinacao_siorg + substring(funcao_sigla, 1, 1) || substring( + funcao_sigla, length(funcao_sigla) - 2, 3 + ) as codigo_combinacao_siorg from codigos_siorg ), + numeracao_cargos_siape as ( + select + *, + row_number() over ( + partition by codigo_combinacao_siape, sigla_uorg_exercicio + order by sigla_uorg_exercicio + ) as ordem_siape + from codigos_siape + ), + + numeracao_cargos_siorg as ( + select + *, + row_number() over ( + partition by codigo_combinacao_siorg, siglaunidade order by siglaunidade + ) as ordem_siorg + from codigo_siorg_combinado + ), + primeira_correlacao as ( select *, @@ -48,22 +111,60 @@ with and siape.codigo_combinacao_siape is not null then 'right' end as tipo_correlacao - from codigo_siorg_combinado as siorg + from numeracao_cargos_siorg as siorg full join - codigos_siape as siape + numeracao_cargos_siape as siape on siorg.codigo_combinacao_siorg = siape.codigo_combinacao_siape and siorg.siglaunidade = siape.sigla_uorg_exercicio + and siorg.ordem_siorg = siape.ordem_siape ), + -- select count(*) from primeira_correlacao tabela_correlacao_cargos as ( - select - cod_funcao as codigo_siape, - funcao as codigo_siorg, + select distinct + pr.cod_funcao as codigo_siape, + pr.funcao_sigla as codigo_siorg, + pr.codigo_combinacao_siape, + pr.codigo_combinacao_siorg, + pr.matricula_siape as matricula_siape, + pr.cpf as cpf, + pr.cpf_chefia_imediata as cpf_chefia_imediata, + pr.cod_situacao_funcional as cod_situacao_funcional, + pr.nome_situacao_funcional as nome_situacao_funcional, + pr.hierarquia_cargo as hierarquia_cargo, + pr.nome_pessoa as servidor, + pr.dt_nascimento as dt_nascimento, + pr.nome_sexo as nome_sexo, + pr.nome_estado_civil as nome_estado_civil, + pr.nome_nacionalidade as nome_nacionalidade, + pr.nome_cor as nome_cor, + pr.uf_nascimento as uf_nascimento, + pr.nome_municipio_nascimento as nome_municipio_nascimento, + dp.nome_pessoa as nome_chefia, + coalesce( + cast(pr.codigounidade as text), cast(pr.codigounidade_alternativa as text) + ) as codigounidade, + coalesce( + cast(pr.codigounidadepai as text), + cast(pr.codigounidadepai_alternativa as text) + ) as codigounidadepai, + coalesce( + cast(pr.caminho_unidade as text), + cast(pr.caminho_unidade_alternativa as text) + ) as caminho_unidade, + coalesce( + cast(pr.ordem_grandeza as text), + cast(pr.ordem_grandeza_alternativa as text) + ) as ordem_grandeza, coalesce(nomeunidade, nome_uorg_exercicio) as nomeunidade, coalesce(siglaunidade, sigla_uorg_exercicio) as siglaunidade, coalesce(denominacao, nome_cargo) as nome_cargo, - matricula_siape - from primeira_correlacao + case + when cod_situacao_funcional = '04' then 'Nomeação livre' else 'Carreira' + end as servidores_carreira + from primeira_correlacao as pr + left join {{ ref("dados_pessoais") }} as dp on pr.cpf_chefia_imediata = dp.cpf + order by caminho_unidade, hierarquia_cargo ) select * diff --git a/airflow_lappis/dags/dbt/ipea/models/schema.yml b/airflow_lappis/dags/dbt/ipea/models/schema.yml index f2991f62..25564123 100644 --- a/airflow_lappis/dags/dbt/ipea/models/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/schema.yml @@ -2120,7 +2120,7 @@ models: # - not_null # - dbt_utils.equal_length: # value: 11 -models: + - name: dados_dependentes description: > Tabela bronze contendo dados de dependentes e benefícios associados, From c828fb5abfe50f840fc563e538be0a94e3354ba9 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Thu, 14 Aug 2025 14:29:24 -0300 Subject: [PATCH 142/317] =?UTF-8?q?feat(visao=5Forcamentaria):=20implement?= =?UTF-8?q?a=20limpeza=20e=20padroniza=C3=A7=C3=A3o=20de=20valores=20para?= =?UTF-8?q?=20inser=C3=A7=C3=A3o=20no=20banco?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../visao_orcamentaria_ingest.py | 50 +++++++++++++++---- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/visao_orcamentaria_ingest.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/visao_orcamentaria_ingest.py index 898679a0..a16ff738 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/visao_orcamentaria_ingest.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/visao_orcamentaria_ingest.py @@ -101,6 +101,23 @@ def _is_valid_data_line(line: str, columns: List[str]) -> bool: and elemento_desp not in ["", '""'] ) + def _clean_value(value: str) -> str: + """ + Limpa e padroniza valores para inserção no banco. + Mantém todos os dados como TEXT para evitar problemas de conversão. + """ + if not value or value in ["nan", "NaN", "None", "null", ""]: + return "" + + # Remove aspas desnecessárias + cleaned = str(value).strip('"').strip() + + # Trata valores especiais + if cleaned in ["nan", "NaN", "None", "null"]: + return "" + + return cleaned + def _process_data_block(data_lines: List[str], year: str) -> List[Dict[str, Any]]: """Processa um bloco de dados de um ano específico.""" block_data = [] @@ -115,12 +132,13 @@ def _process_data_block(data_lines: List[str], year: str) -> List[Dict[str, Any] # Remove aspas e limpa colunas columns = [col.strip('"').strip() for col in columns] - # Cria registro com mapeamento de colunas - row_data = { - col_name: columns[col_index] if col_index < len(columns) else "" - for col_index, col_name in COLUMN_MAPPING.items() - } - row_data["ano_exercicio"] = year + # Cria registro com mapeamento de colunas e limpeza de valores + row_data = {} + for col_index, col_name in COLUMN_MAPPING.items(): + raw_value = columns[col_index] if col_index < len(columns) else "" + row_data[col_name] = _clean_value(raw_value) + + row_data["ano_exercicio"] = str(year) block_data.append(row_data) return block_data @@ -219,12 +237,15 @@ def process_email_data(**context: Dict[str, Any]) -> Optional[str]: logging.warning("Nenhum dado foi processado do CSV.") return None - # Converte para CSV string df = pd.DataFrame(processed_data) # Adicionar dt_ingest a cada registro df["dt_ingest"] = datetime.now().isoformat() + # Garantir que todos os valores sejam strings para evitar problemas de tipo + for col in df.columns: + df[col] = df[col].astype(str) + csv_string = df.to_csv(index=False) logging.info( @@ -248,6 +269,13 @@ def insert_and_clean_data(**context: Dict[str, Any]) -> None: return df = pd.read_csv(io.StringIO(csv_data)) + + # Garantir que todos os valores sejam strings para evitar problemas de tipo + for col in df.columns: + df[col] = df[col].astype(str) + # Limpar valores NaN/None que podem ter sido introduzidos pelo pandas + df[col] = df[col].replace(["nan", "NaN", "None"], "") + data = df.to_dict(orient="records") postgres_conn_str = get_postgres_conn() @@ -266,9 +294,13 @@ def insert_and_clean_data(**context: Dict[str, Any]) -> None: ) # Remove duplicados - column_mapping_with_year = {**COLUMN_MAPPING, 30: "ano_exercicio"} + column_mapping_with_year = { + **COLUMN_MAPPING, + 30: "ano_exercicio", + 31: "dt_ingest", + } db.remove_duplicates( - "visao_orcamentaria_total_ipea", column_mapping_with_year, schema="siafi" + "visao_orcamentaria_total", column_mapping_with_year, schema="siafi" ) logging.info("Limpeza de duplicados concluída com sucesso.") From 5b1b0018121d6bd55da12c00f15d67b8eefe2010 Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira Date: Fri, 15 Aug 2025 19:48:40 +0000 Subject: [PATCH 143/317] fix(dag): ajusta e refatora ingestao para nota de credito --- .../nc_tesouro_ingest.dag.py | 166 ++++++++---------- .../visao_orcamentaria_ingest.py | 1 - 2 files changed, 78 insertions(+), 89 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/nc_tesouro_ingest.dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/nc_tesouro_ingest.dag.py index dea14f13..18353a62 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/nc_tesouro_ingest.dag.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/nc_tesouro_ingest.dag.py @@ -1,4 +1,4 @@ -from typing import Dict, Any, Optional, List, cast +from typing import Dict, Any, cast from airflow import DAG from airflow.operators.python import PythonOperator from airflow.models import Variable @@ -43,9 +43,19 @@ 19: "movimento_liquido", } -# Assuntos dos emails a serem processados -EMAIL_SUBJECT_ENVIADAS = "notas_credito_enviadas_devolvidas_a_partir_de_2024" -EMAIL_SUBJECT_RECEBIDAS = "notas_credito_recebidas_a_partir_de_2024" +# Configurações dos emails +EMAIL_CONFIGS = { + "enviadas": { + "subject": "notas_credito_enviadas_devolvidas_a_partir_de_2024", + "column_mapping": COLUMN_MAPPING, + "skiprows": 7, + }, + "recebidas": { + "subject": "notas_credito_recebidas_a_partir_de_2024", + "column_mapping": None, + "skiprows": 3, + }, +} # Configurações da DAG with DAG( @@ -58,111 +68,93 @@ tags=["email", "ncs", "tesouro"], ) as dag: - def process_email_data_enviadas(**context: Dict[str, Any]) -> Optional[List[Dict]]: + def process_email_data(email_type: str, **context: Dict[str, Any]) -> pd.DataFrame: """ - Função para processar os emails com notas de crédito enviadas. + Função genérica para processar emails de notas de crédito. """ - creds = json.loads(Variable.get("email_credentials")) - - EMAIL = creds["email"] - PASSWORD = creds["password"] - IMAP_SERVER = creds["imap_server"] - SENDER_EMAIL = creds["sender_email"] + config = EMAIL_CONFIGS[email_type] + creds_data = json.loads(Variable.get("email_credentials")) + creds = cast(Dict[str, str], creds_data) + config = cast(Dict[str, Any], config) try: - logging.info("Iniciando o processamento das NCs enviadas/devolvidas") - csv_data = cast( - Optional[List[Dict[Any, Any]]], - fetch_and_process_email( - IMAP_SERVER, - EMAIL, - PASSWORD, - SENDER_EMAIL, - EMAIL_SUBJECT_ENVIADAS, - COLUMN_MAPPING, - skiprows=7, - ), + logging.info(f"Iniciando o processamento das NCs {email_type}") + csv_data = fetch_and_process_email( + creds["imap_server"], + creds["email"], + creds["password"], + creds["sender_email"], + config["subject"], + config["column_mapping"], + skiprows=config["skiprows"], ) - if not csv_data: - logging.warning("Nenhum e-mail encontrado com o assunto de NCs enviadas") - return [] - logging.info( - "CSV de NCs enviadas processado com sucesso. Dados encontrados: %s", - len(csv_data), - ) - return csv_data - except Exception as e: - logging.error( - "Erro no processamento dos emails de notas de crédito enviadas: %s", - str(e), - ) - raise - - def process_email_data_recebidas(**context: Dict[str, Any]) -> Optional[List[Dict]]: - """ - Função para processar os emails com notas de crédito recebidas. - """ - creds = json.loads(Variable.get("email_credentials")) + if not csv_data: + logging.warning(f"Nenhum e-mail encontrado para NCs {email_type}") + return pd.DataFrame() - EMAIL = creds["email"] - PASSWORD = creds["password"] - IMAP_SERVER = creds["imap_server"] - SENDER_EMAIL = creds["sender_email"] + df = pd.read_csv(io.StringIO(csv_data)) - try: - logging.info("Iniciando o processamento das NCs recebidas...") - csv_data = cast( - Optional[List[Dict[Any, Any]]], - fetch_and_process_email( - IMAP_SERVER, - EMAIL, - PASSWORD, - SENDER_EMAIL, - EMAIL_SUBJECT_RECEBIDAS, - column_mapping=None, - skiprows=3, - ), - ) - if not csv_data: - logging.warning( - "Nenhum e-mail encontrado com o assunto de NCs recebidas." - ) - return [] + # Se não tem mapeamento de colunas (recebidas), aplicar o mapeamento padrão + if config["column_mapping"] is None and not df.empty: + expected_columns = list(COLUMN_MAPPING.values()) + if len(df.columns) == len(expected_columns): + df.columns = pd.Index(expected_columns) + else: + logging.warning( + f"N coluna incompatível:{len(expected_columns)},{len(df.columns)}" + ) logging.info( - "CSV de NCs recebidas processado com sucesso. Dados encontrados: %s", - len(csv_data), + f"CSV de NCs {email_type} processado com sucesso: {len(df)} registros" ) - return csv_data + return df + except Exception as e: logging.error( - "Erro no processamento dos emails de notas de crédito recebidas: %s", - str(e), + f"Erro no processamento dos emails de NCs {email_type}: {str(e)}" ) raise - def combine_data(**context: Dict[str, Any]) -> List[Dict]: + def process_email_data_enviadas(**context: Dict[str, Any]) -> pd.DataFrame: + """Wrapper para processar emails enviadas.""" + return process_email_data("enviadas", **context) + + def process_email_data_recebidas(**context: Dict[str, Any]) -> pd.DataFrame: + """Wrapper para processar emails recebidas.""" + return process_email_data("recebidas", **context) + + def combine_data(**context: Dict[str, Any]) -> pd.DataFrame: """ Função para combinar os dados dos dois emails. """ try: task_instance: Any = context["ti"] - enviadas_data = ( - task_instance.xcom_pull(task_ids="process_emails_enviadas") or [] + df_enviadas = cast( + pd.DataFrame, task_instance.xcom_pull(task_ids="process_emails_enviadas") ) - recebidas_data = ( - task_instance.xcom_pull(task_ids="process_emails_recebidas") or [] + df_recebidas = cast( + pd.DataFrame, task_instance.xcom_pull(task_ids="process_emails_recebidas") ) - combined_data = enviadas_data + recebidas_data + # Combinar DataFrames válidos + dfs = [ + df + for df in [df_enviadas, df_recebidas] + if df is not None and not df.empty + ] + + if not dfs: + logging.warning("Nenhum dado foi encontrado para combinar.") + return pd.DataFrame() + + # Combinar os DataFrames e adicionar dt_ingest + combined_df = pd.concat(dfs, ignore_index=True) + combined_df["dt_ingest"] = datetime.now().isoformat() - # Adicionar dt_ingest a cada registro - for record in combined_data: - record["dt_ingest"] = datetime.now().isoformat() + logging.info(f"Dados combinados: {len(combined_df)} registros no total.") + return combined_df - logging.info(f"Dados combinados: {len(combined_data)} registros no total.") - return combined_data except Exception as e: logging.error(f"Erro ao combinar os dados: {str(e)}") raise @@ -170,18 +162,16 @@ def combine_data(**context: Dict[str, Any]) -> List[Dict]: def insert_data_to_db(**context: Dict[str, Any]) -> None: """ Função para inserir os dados no banco de dados. - Os dados combinados são recuperados do XCom. """ try: task_instance: Any = context["ti"] - combined_data = task_instance.xcom_pull(task_ids="combine_data") + combined_df = task_instance.xcom_pull(task_ids="combine_data") - if not combined_data: + if combined_df is None or combined_df.empty: logging.warning("Nenhum dado para inserir no banco.") return - df = pd.read_csv(io.StringIO(combined_data)) - data = df.to_dict(orient="records") + data = combined_df.to_dict(orient="records") postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/visao_orcamentaria_ingest.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/visao_orcamentaria_ingest.py index a16ff738..57184ab4 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/visao_orcamentaria_ingest.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/visao_orcamentaria_ingest.py @@ -297,7 +297,6 @@ def insert_and_clean_data(**context: Dict[str, Any]) -> None: column_mapping_with_year = { **COLUMN_MAPPING, 30: "ano_exercicio", - 31: "dt_ingest", } db.remove_duplicates( "visao_orcamentaria_total", column_mapping_with_year, schema="siafi" From 75df421be173613f964020e6fac488ac3ee1874f Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Sat, 16 Aug 2025 15:50:54 -0300 Subject: [PATCH 144/317] delete(dags): exclui dags inutilizadas --- .../dags/data_ingest/admin/drop_tables_dag.py | 55 ------- .../estagios_tesouro_ingest_dag.py | 137 ------------------ 2 files changed, 192 deletions(-) delete mode 100644 airflow_lappis/dags/data_ingest/admin/drop_tables_dag.py delete mode 100755 airflow_lappis/dags/data_ingest/tesouro_gerencial/estagios_tesouro_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/admin/drop_tables_dag.py b/airflow_lappis/dags/data_ingest/admin/drop_tables_dag.py deleted file mode 100644 index 89d3b0e1..00000000 --- a/airflow_lappis/dags/data_ingest/admin/drop_tables_dag.py +++ /dev/null @@ -1,55 +0,0 @@ -import logging -from airflow.decorators import dag, task -from datetime import datetime, timedelta -from airflow.models import Param -from postgres_helpers import get_postgres_conn -from cliente_postgres import ClientPostgresDB -from typing import Dict - - -@dag( - schedule_interval=None, - start_date=datetime(2023, 1, 1), - catchup=False, - default_args={ - "owner": "admin", - "retries": 0, - "retry_delay": timedelta(minutes=5), - }, - tags=["siape", "admin", "drop"], - params={ - "tabela": Param("", description="Nome da tabela a ser dropada"), - "schema": Param("", description="Schema onde está a tabela"), - }, -) -def drop_tabela_parametrizada_dag() -> None: - """ - DAG para remover uma tabela específica de um schema no Postgres. - Tabela e schema devem ser informados via parâmetros 'tabela' e 'schema'. - """ - - @task - def drop_tabela(task_params: Dict[str, str]) -> None: - tabela = task_params.get("tabela") - schema = task_params.get("schema") - - if not tabela: - raise ValueError("Parâmetro 'tabela' não informado.") - if not schema: - raise ValueError("Parâmetro 'schema' não informado.") - - logging.info(f"Iniciando remoção da tabela {schema}.{tabela}") - conn_str = get_postgres_conn() - db = ClientPostgresDB(conn_str) - - try: - db.drop_table_if_exists(tabela, schema=schema) - logging.info(f"Tabela {schema}.{tabela} removida com sucesso.") - except Exception as e: - logging.error(f"Erro ao remover tabela {schema}.{tabela}: {e}") - raise - - drop_tabela({"tabela": "{{ params.tabela }}", "schema": "{{ params.schema }}"}) - - -dag_instance = drop_tabela_parametrizada_dag() diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/estagios_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/estagios_tesouro_ingest_dag.py deleted file mode 100755 index f3972327..00000000 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/estagios_tesouro_ingest_dag.py +++ /dev/null @@ -1,137 +0,0 @@ -from typing import Optional, Dict, Any -from airflow import DAG -from airflow.operators.python import PythonOperator -from airflow.models import Variable -from datetime import datetime, timedelta -import logging -import json -from postgres_helpers import get_postgres_conn -from cliente_email import fetch_and_process_email -from cliente_postgres import ClientPostgresDB - -# Configurações básicas da DAG -default_args = { - "owner": "Davi", - "depends_on_past": False, - "retries": 1, - "retry_delay": timedelta(minutes=5), -} - -COLUMN_MAPPING = { - 0: "ne_ccor", - 1: "ne_informacao_complementar", - 2: "ne_num_processo", - 3: "ne_ccor_descricao", - 4: "doc_observacao", - 5: "natureza_despesa", - 6: "natureza_despesa_1", - 7: "natureza_despesa_detalhada", - 8: "natureza_despesa_detalhada_1", - 9: "ne_ccor_favorecido", - 10: "ne_ccor_favorecido_1", - 11: "ano_lancamento", - 12: "ne_ccor_mes_emissao", - 13: "ne_ccor_ano_emissao", - 14: "mes_lancamento", - 15: "despesas_empenhadas_controle_empenho_saldo_moeda_origem", - 16: "despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem", - 17: "despesas_liquidadas_controle_empenho_saldo_moeda_origem", - 18: "despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem", - 19: "despesas_pagas_controle_empenho_saldo_moeda_origem", - 20: "despesas_pagas_controle_empenho_movim_liquido_moeda_origem", -} - -EMAIL_SUBJECT = "consulta_por_execucao_emp_liq_pago_mensal" -SKIPROWS = 5 - -# Configurações da DAG -with DAG( - dag_id="email_estagios_tesouro_ingest", - default_args=default_args, - description="Processa anexos dos estagios vindo do email, formata e insere no db", - schedule_interval="0 13 * * 1-6", - start_date=datetime(2023, 12, 1), - catchup=False, - tags=["email", "estagios", "tesouro"], -) as dag: - - def process_email_data(**context: Dict[str, Any]) -> Optional[str]: - creds = json.loads(Variable.get("email_credentials")) - - EMAIL = creds["email"] - PASSWORD = creds["password"] - IMAP_SERVER = creds["imap_server"] - SENDER_EMAIL = creds["sender_email"] - - try: - logging.info("Iniciando o processamento dos emails...") - csv_data = fetch_and_process_email( - IMAP_SERVER, - EMAIL, - PASSWORD, - SENDER_EMAIL, - EMAIL_SUBJECT, - COLUMN_MAPPING, - skiprows=SKIPROWS, - ) - if not csv_data: - logging.warning("Nenhum e-mail encontrado com o assunto esperado.") - return None - - logging.info( - "CSV processado com sucesso. Dados encontrados: %s", len(csv_data) - ) - return str(csv_data) if csv_data is not None else None - except Exception as e: - logging.error("Erro no processamento dos emails: %s", str(e)) - raise - - def insert_data_to_db(**context: Dict[str, Any]) -> None: - """ - Função para inserir os dados no banco de dados. - Os dados do CSV são recuperados do XCom. - """ - try: - task_instance: Any = context["ti"] - csv_data: Any = task_instance.xcom_pull(task_ids="process_emails") - - if not csv_data: - logging.warning("Nenhum dado para inserir no banco.") - return - - postgres_conn_str = get_postgres_conn() - db = ClientPostgresDB(postgres_conn_str) - - # Adicionar dt_ingest aos dados antes da inserção - # Se csv_data for uma string, precisamos convertê-la para uma lista de dicts - if isinstance(csv_data, str): - import ast - - csv_data = ast.literal_eval(csv_data) - - # Adicionar dt_ingest a cada registro - for record in csv_data: - record["dt_ingest"] = datetime.now().isoformat() - - db.insert_data(csv_data, "estagios_tesouro", schema="siafi") - logging.info("Dados inseridos com sucesso no banco de dados.") - except Exception as e: - logging.error("Erro ao inserir dados no banco: %s", str(e)) - raise - - # Tarefa 1: Processar os e-mails e retornar CSV - process_emails_task = PythonOperator( - task_id="process_emails", - python_callable=process_email_data, - provide_context=True, - ) - - # Tarefa 2: Inserir os dados no banco de dados - insert_to_db_task = PythonOperator( - task_id="insert_to_db", - python_callable=insert_data_to_db, - provide_context=True, - ) - - # Fluxo da DAG - process_emails_task >> insert_to_db_task From 6794e4c07f951b86206f0f8b1eab5ce8e2a4a8a8 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Mon, 18 Aug 2025 14:22:06 -0300 Subject: [PATCH 145/317] feat(dbt): adiciona modelo dbt para visao orcamentaria --- airflow_lappis/dags/dbt/ipea/dbt_project.yml | 5 ++ .../dbt/ipea/macros/parse_financial_value.sql | 2 + .../bronze/visao_orcamentaria_total.sql | 47 +++++++++++++++++++ .../dags/dbt/ipea/models/sources.yml | 1 + 4 files changed, 55 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/orcamento_dbt/bronze/visao_orcamentaria_total.sql diff --git a/airflow_lappis/dags/dbt/ipea/dbt_project.yml b/airflow_lappis/dags/dbt/ipea/dbt_project.yml index 5cb9f39f..fab017ca 100755 --- a/airflow_lappis/dags/dbt/ipea/dbt_project.yml +++ b/airflow_lappis/dags/dbt/ipea/dbt_project.yml @@ -37,6 +37,11 @@ models: +schema: ted views: +materialized: view + orcamento_dbt: + +materialized: table + +schema: orcamento + views: + +materialized: view # siape_dbt: # +materialized: table # +schema: siape diff --git a/airflow_lappis/dags/dbt/ipea/macros/parse_financial_value.sql b/airflow_lappis/dags/dbt/ipea/macros/parse_financial_value.sql index 86dfe861..437b673c 100644 --- a/airflow_lappis/dags/dbt/ipea/macros/parse_financial_value.sql +++ b/airflow_lappis/dags/dbt/ipea/macros/parse_financial_value.sql @@ -1,6 +1,8 @@ {% macro parse_financial_value(column_name) %} case + when {{ column_name }} is null or trim({{ column_name }}) = '' + then 0.00::numeric(15, 2) when {{ column_name }} like '%NaN%' then 0.00::numeric(15, 2) when {{ column_name }} like '(%' diff --git a/airflow_lappis/dags/dbt/ipea/models/orcamento_dbt/bronze/visao_orcamentaria_total.sql b/airflow_lappis/dags/dbt/ipea/models/orcamento_dbt/bronze/visao_orcamentaria_total.sql new file mode 100644 index 00000000..78edd3c7 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/orcamento_dbt/bronze/visao_orcamentaria_total.sql @@ -0,0 +1,47 @@ +with + source_data as (select * from {{ source("siafi", "visao_orcamentaria_total") }}), + + typed_data as ( + select + ano_exercicio, + unidade_orcamentaria, + unidade_orcamentaria_desc, + acao_governo, + acao_governo_desc, + programa_governo, + programa_governo_desc, + unidade_plano_orcamentario, + plano_orcamentario_1, + plano_orcamentario_2, + programa_plano_orcamentario, + acao_plano_orcamentario, + plano_orcamentario_6, + plano_orcamentario_desc, + elemento_despesa, + elemento_despesa_desc, + orgao_uge, + orgao_uge_desc, + uge_matriz_filial, + ug_executora, + ug_executora_desc, + + -- Campos financeiros/monetários + {{ parse_financial_value("projeto_inicial_loa") }} as projeto_inicial_loa, + {{ parse_financial_value("dotacao_inicial") }} as dotacao_inicial, + {{ parse_financial_value("dotacao_atualizada") }} as dotacao_atualizada, + {{ parse_financial_value("credito_disponivel") }} as credito_disponivel, + {{ parse_financial_value("despesas_empenhadas") }} as despesas_empenhadas, + {{ parse_financial_value("despesas_a_liquidar") }} as despesas_a_liquidar, + {{ parse_financial_value("despesar_a_pagar") }} as despesar_a_pagar, + {{ parse_financial_value("despesas_pagas") }} as despesas_pagas, + {{ parse_financial_value("restos_a_pagar_inscritos") }} + as restos_a_pagar_inscritos, + {{ parse_financial_value("restos_a_pagar_pagos") }} as restos_a_pagar_pagos, + + dt_ingest::timestamp as dt_ingest + + from source_data + ) + +select * +from typed_data diff --git a/airflow_lappis/dags/dbt/ipea/models/sources.yml b/airflow_lappis/dags/dbt/ipea/models/sources.yml index edf337f7..6635fbf8 100644 --- a/airflow_lappis/dags/dbt/ipea/models/sources.yml +++ b/airflow_lappis/dags/dbt/ipea/models/sources.yml @@ -17,6 +17,7 @@ sources: - name: estagios_tesouro - name: nc_tesouro - name: pf_tesouro + - name: visao_orcamentaria_total - name: transfere_gov schema: transfere_gov From dac20e168feb53493abc611238c30900ea1a2c80 Mon Sep 17 00:00:00 2001 From: Mateus de Castro Date: Tue, 19 Aug 2025 23:23:08 +0000 Subject: [PATCH 146/317] Dbt docs --- .../dags/dbt/ipea/models/schema.yml | 3945 ++++++++--------- 1 file changed, 1908 insertions(+), 2037 deletions(-) diff --git a/airflow_lappis/dags/dbt/ipea/models/schema.yml b/airflow_lappis/dags/dbt/ipea/models/schema.yml index 25564123..526f345f 100644 --- a/airflow_lappis/dags/dbt/ipea/models/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/schema.yml @@ -1,6 +1,3 @@ -# Este arquivo deve ser usado para descrições dos modelos -# e para configurar testes - version: 2 models: @@ -15,6 +12,9 @@ models: A tabela é atualizada diariamente e contém dados de contratos firmados pelo IPEA. A tabela realiza validações e limpezas dos dados extraídos do ComprasGov, incluindo formatação adequada de valores numéricos, remoção de caracteres especiais em CPF/CNPJ e normalização de datas. + meta: + tags: + - bronze columns: - name: id description: > @@ -33,13 +33,13 @@ models: Nome do fornecedor contratado. - name: tipo description: > - Tipo de contrato (ex: Contrato, Empenho, Termo de Compromisso, etc.). + Tipo de contrato ( Contrato, Empenho, Termo de Compromisso, etc.). - name: situacao description: > - Situação atual do contrato (ex: Ativo, Inativo). + Situação atual do contrato ( Ativo, Inativo). - name: categoria description: > - Categoria do contrato (ex: Informática, Serviços, Mão de Obra, Serviços de Engenharia, etc.). + Categoria do contrato ( Informática, Serviços, Mão de Obra, Serviços de Engenharia, etc.). - name: objeto description: > Descrição do objeto contratado. @@ -48,7 +48,7 @@ models: Código que identifica a modalidade do contrato. - name: modalidade description: > - Modalidade do contrato (ex: Pregão, Dispensa, Inexigibilidade, etc.). + Modalidade do contrato ( Pregão, Dispensa, Inexigibilidade, etc.). - name: num_parcelas description: > Número de parcelas para pagamento do contrato. @@ -132,6 +132,9 @@ models: Essa tabela contém informações sobre as despesas mensais programadas para cada contrato. Os dados são extraídos da tabela cronograma do ComprasGov, com valores formatados adequadamente. Apresenta o planejamento financeiro do contrato distribuído em parcelas mensais. + meta: + tags: + - bronze columns: - name: id description: > @@ -185,6 +188,9 @@ models: Essa tabela contém informações sobre as faturas emitidas mensalmente de cada contrato. Os dados são extraídos da tabela faturas do ComprasGov com formatação adequada de valores numéricos e datas. A tabela inclui um processamento adicional para extrair informações dos empenhos a partir do campo JSON dados_empenho. + meta: + tags: + - bronze columns: - name: id description: > @@ -300,6 +306,9 @@ models: Essa tabela contém informações sobre os empenhos de cada contrato extraídos do ComprasGov. Os dados são formatados adequadamente para garantir consistência nos tipos numéricos e de data. Registra os compromissos de pagamento assumidos pela administração para cada contrato. + meta: + tags: + - bronze columns: - name: id description: > @@ -389,6 +398,9 @@ models: Essa tabela contém informações sobre movimentação financeira dos empenhos extraídos do SIAFI. Contém dados de empenhos do Tesouro Nacional com formatação de valores financeiros através da macro parse_financial_value. Apresenta valores para as diferentes etapas da execução orçamentária (empenho, liquidação, pagamento). + meta: + tags: + - bronze columns: - name: emissao_mes description: > @@ -458,6 +470,9 @@ models: Extrai dados da tabela estagios_tesouro do SIAFI com formatação e padronização para facilitar análises. Trata especificamente valores monetários com parênteses, convertendo-os para números negativos. Apresenta os diferentes estágios da despesa (empenho, liquidação, pagamento) ao longo do tempo. + meta: + tags: + - bronze columns: - name: ne_ccor description: > @@ -556,6 +571,9 @@ models: Realiza uma complexa estratégia de join entre os contratos e empenhos do tesouro, tentando várias abordagens para relacionar os dados do SIAFI com os contratos do ComprasGov usando: 1) número de empenho e CNPJ/CPF, 2) número de processo, 3) CNPJ/CPF único, e 4) informação complementar. + meta: + tags: + - silver columns: - name: contrato_id description: > @@ -606,6 +624,9 @@ models: Utiliza uma estratégia de join em cascata, tentando correlacionar os estágios das despesas do SIAFI com os contratos do ComprasGov através de várias combinações de chaves, incluindo número de empenho, CNPJ/CPF, número de processo e informações complementares. + meta: + tags: + - silver columns: - name: contrato_id description: > @@ -628,6 +649,9 @@ models: Essa tabela discrimina os valores empenhados, liquidados e pagos mensalmente extraídos do SIAFI para cada contrato. Transforma e agrega os dados da tabela empenhos_tesouro do SIAFI, padronizando formatos e consolidando valores por número de empenho, mês e CNPJ/CPF. + meta: + tags: + - silver columns: - name: ne description: > @@ -659,6 +683,9 @@ models: Essa tabela integra informações detalhadas de faturas com dados dos contratos relacionados. Permite uma visão completa de cada fatura no contexto do seu contrato, incluindo dados como número de contrato, processo, objeto e fornecedor. Facilita a análise financeira das faturas em conjunto com seus respectivos contratos. + meta: + tags: + - silver columns: - name: id description: > @@ -763,11 +790,14 @@ models: description: > Código do subelemento de despesa relacionado à fatura. - - name: cronogramas_fatura_mensal + - name: cronogramas_faturas_mensal description: > Essa tabela discrimina os valores programados e faturados mensalmente pelo ComprasGov para cada contrato. Combina dados dos cronogramas com as faturas pagas e pendentes, agrupando por contrato e mês de referência, e calculando o saldo contratual disponível (diferença entre o valor programado e os valores faturados). + meta: + tags: + - silver columns: - name: contrato_id description: > @@ -788,2289 +818,2120 @@ models: description: > Diferença entre o valor programado no cronograma e a soma dos valores faturados (pagos e pendentes). - #Siape - - name: servidores_detalhados + + + ## Golds + - name: contratos_resumo description: > - Esta tabela consolida informações detalhadas sobre os servidores, combinando dados pessoais, funcionais, - educacionais (uma das formações ou todas, dependendo da cardinalidade da junção com dados_escolares), - informações da última transação e detalhes da UORG associada ao servidor. - Serve como uma base rica para análises e relatórios sobre o quadro de pessoal. + Essa tabela contém um resumo dos contratos, informações contratuais como valor global e valor pago, + e situação atual, como em vigência ou pendente de baixa. + Facilita a análise gerencial dos contratos com indicadores chave como status de pagamento, tipo de fornecedor, + e identificação de contratos continuados (com vigência superior a dois anos e mais de uma parcela). + meta: + tags: + - gold columns: - - name: cpf - description: > - CPF do servidor, utilizado como chave primária para junção de dados. - - name: nome_pessoa - description: > - Nome completo do servidor. - - name: dt_nascimento - description: > - Data de nascimento do servidor. - - name: grupo_sanguineo - description: > - Grupo sanguíneo do servidor. - - name: nome_cor - description: > - Nome da cor ou raça declarada pelo servidor. - - name: cod_cor - description: > - Código da cor ou raça declarada pelo servidor. - - name: nome_estado_civil - description: > - Nome do estado civil do servidor. - - name: cod_estado_civil - description: > - Código do estado civil do servidor. - - name: nome_mae - description: > - Nome da mãe do servidor. - - name: nome_pai - description: > - Nome do pai do servidor. - - name: nome_municipio_nascimento - description: > - Nome do município de nascimento do servidor. - - name: nome_nacionalidade - description: > - Nome da nacionalidade do servidor. - - name: cod_nacionalidade - description: > - Código da nacionalidade do servidor. - - name: nome_sexo - description: > - Nome do sexo declarado pelo servidor. - - name: cod_sexo - description: > - Código do sexo declarado pelo servidor. - - name: num_pispasep - description: > - Número do PIS/PASEP do servidor. - - name: uf_nascimento - description: > - UF (Unidade Federativa) de nascimento do servidor. - - name: cod_deficiencia_fisica - description: > - Código da deficiência física do servidor, se houver. - - name: nome_deficiencia_fisica - description: > - Nome da deficiência física do servidor, se houver. - - name: dt_chegada_brasil - description: > - Data de chegada ao Brasil, para servidores estrangeiros. - - name: nome_pais_origem - description: > - Nome do país de origem, para servidores estrangeiros. - - name: cod_atividade_funcao - description: > - Código da atividade da função desempenhada pelo servidor. - - name: cod_funcao - description: > - Código da função ou cargo comissionado do servidor. - - name: cod_jornada - description: > - Código da jornada de trabalho do servidor. - - name: cod_ocorr_ingresso_orgao - description: > - Código da ocorrência de ingresso do servidor no órgão. - - name: cod_ocorr_ingresso_serv_publico - description: > - Código da ocorrência de ingresso do servidor no serviço público. - - name: cod_orgao_funcional - description: > - Código do órgão funcional do servidor. - - name: cod_padrao - description: > - Código do padrão do cargo do servidor. - - name: cod_situacao_funcional - description: > - Código da situação funcional do servidor. - - name: cod_uorg_exercicio - description: > - Código da UORG (Unidade Organizacional) de exercício do servidor. - - name: cod_upag - description: > - Código da UPAG (Unidade Pagadora) do servidor. - - name: cod_orgao_origem - description: > - Código do órgão de origem do servidor. - - name: cpf_chefia_imediata - description: > - CPF da chefia imediata do servidor. - - name: dt_exercicio_no_orgao - description: > - Data de início do exercício do servidor no órgão. - - name: dt_fim_vale_ar - description: > - Data de término do Vale AR (contexto específico do SIAPE). - - name: dt_ingresso_funcao - description: > - Data de ingresso do servidor na função ou cargo comissionado. - - name: dt_ocorr_ingresso_orgao - description: > - Data da ocorrência de ingresso do servidor no órgão. - - name: dt_ocorr_ingresso_serv_publico - description: > - Data da ocorrência de ingresso do servidor no serviço público. - - name: email_chefia_imediata - description: > - Email da chefia imediata do servidor. - - name: email_institucional - description: > - Email institucional do servidor. - - name: email_servidor - description: > - Email pessoal do servidor (registrado no sistema). - - name: ident_unica_funcional - description: > - Identificador único funcional do servidor. - - name: matricula_siape - description: > - Matrícula SIAPE do servidor. - - name: modalidade_pgd - description: > - Modalidade do PGD (Programa de Gestão de Desempenho) do servidor. - - name: nome_atividade_funcao - description: > - Nome da atividade da função desempenhada pelo servidor. - - name: nome_chefe_uorg - description: > - Nome do chefe da UORG do servidor. - - name: nome_funcao - description: > - Nome da função ou cargo comissionado do servidor. - - name: nome_jornada - description: > - Nome da jornada de trabalho do servidor. - - name: nome_ocorr_ingresso_orgao - description: > - Nome da ocorrência de ingresso do servidor no órgão. - - name: nome_ocorr_ingresso_serv_publico - description: > - Nome da ocorrência de ingresso do servidor no serviço público. - - name: nome_orgao_funcional - description: > - Nome do órgão funcional do servidor. - - name: nome_regime_juridico - description: > - Nome do regime jurídico do servidor. - - name: nome_situacao_funcional - description: > - Nome da situação funcional do servidor. - - name: nome_uorg_exercicio - description: > - Nome da UORG de exercício do servidor. - - name: nome_upag + - name: contrato_id description: > - Nome da UPAG (Unidade Pagadora) do servidor. - - name: participa_pgd + Identificador único do contrato. + - name: fornecedor_cnpj_cpf description: > - Indicador se o servidor participa do PGD. - - name: percentual_ts + CNPJ ou CPF do fornecedor contratado, com formatação padronizada. + - name: numero description: > - Percentual de Tempo de Serviço. - - name: sigla_orgao_funcional + Número do contrato no sistema ComprasGov. + - name: categoria description: > - Sigla do órgão funcional do servidor. - - name: sigla_orgao_origem + Categoria do contrato ( Informática, Serviços, Mão de Obra). + - name: modalidade description: > - Sigla do órgão de origem do servidor. - - name: sigla_regime_juridico + Modalidade de licitação utilizada na contratação. + - name: tipo description: > - Sigla do regime jurídico do servidor. - - name: sigla_uorg_exercicio + Tipo de contrato ( Contrato, Empenho, Termo de Compromisso). + - name: situacao description: > - Sigla da UORG de exercício do servidor. - - name: sigla_upag + Situação atual do contrato (Ativo ou Inativo). + - name: pendente_baixa description: > - Sigla da UPAG (Unidade Pagadora) do servidor. - - name: cod_cargo + Indica se o contrato está pendente de baixa ('Sim' quando o valor pago for igual ao valor global, 'Não' caso contrário). + - name: fornecedor_nome description: > - Código do cargo efetivo do servidor. - - name: cod_classe + Nome ou razão social do fornecedor contratado. + - name: objeto description: > - Código da classe do cargo do servidor. - - name: cod_ocorr_aposentadoria + Descrição detalhada do objeto contratado. + - name: valor_global description: > - Código da ocorrência de aposentadoria do servidor. - - name: dt_ini_vale_ar + Valor total do contrato após eventuais aditivos. + - name: despesas_pagas description: > - Data de início do Vale AR (contexto específico do SIAPE). - - name: dt_ocorr_aposentadoria + Valor total já pago do contrato conforme registros do SIAFI. + - name: vigencia_inicio description: > - Data da ocorrência de aposentadoria do servidor. - - name: nome_cargo + Data de início da vigência do contrato. + - name: vigencia_fim description: > - Nome do cargo efetivo do servidor. - - name: nome_classe + Data de término da vigência do contrato. + - name: num_parcelas description: > - Nome da classe do cargo do servidor. - - name: nome_ocorr_aposentadoria + Número de parcelas para pagamento do contrato. + - name: fornecedor_tipo description: > - Nome da ocorrência de aposentadoria do servidor. - - name: sigla_nivel_cargo + Tipo do fornecedor, categorizado como 'Empresa do Exterior' para IDGENERICO. + - name: Unidade description: > - Sigla do nível do cargo do servidor. - - name: tipo_vale_ar + Unidade gestora responsável pelo contrato, no formato "código - nome_resumido". + - name: continuado description: > - Tipo do Vale AR (contexto específico do SIAPE). - - name: cod_ocorr_isencao_ir + Indica se o contrato é de prestação continuada ('Sim' quando a vigência for maior que 730 dias e tiver mais de uma parcela). + + - name: contratos_comparativo_mensal + description: > + Essa tabela contém um comparativo mensal dos contratos, discriminando + os valores empenhados, liquidados e pagos do SIAFI, + programados e faturados do ComprasGov. + Permite a identificação de inconsistências entre os sistemas e o acompanhamento detalhado + da execução financeira mensal de cada contrato. + meta: + tags: + - gold + columns: + - name: contrato_id description: > - Código da ocorrência de isenção de Imposto de Renda do servidor. - - name: dt_ini_ocorr_isencao_ir + Identificador único do contrato. + - name: mes_ref description: > - Data de início da ocorrência de isenção de IR do servidor. - - name: nome_ocorr_isencao_ir + Mês de referência da informação financeira, preenchido para todos os meses entre o início e fim do contrato. + - name: comprasgov_valor_cronograma description: > - Nome da ocorrência de isenção de IR do servidor. - - name: cod_uorg_lotacao + Valor programado para o mês conforme cronograma registrado no ComprasGov. + - name: comprasgov_valor_faturas description: > - Código da UORG de lotação do servidor. - - name: nome_uorg_lotacao + Soma dos valores das faturas (pagas e pendentes) no mês de referência conforme ComprasGov. + - name: comprasgov_saldo_contratual_disponivel description: > - Nome da UORG de lotação do servidor. - - name: sigla_uorg_lotacao + Diferença entre o valor programado e o valor faturado no mês, indicando o saldo disponível. + - name: siafi_valor_empenhado description: > - Sigla da UORG de lotação do servidor. - - name: dt_fim_ocorr_isencao_ir + Valor empenhado no mês de referência conforme registros do SIAFI. + - name: siafi_valor_liquidado description: > - Data de fim da ocorrência de isenção de IR do servidor. - - name: cod_ocorr_exclusao + Valor liquidado no mês de referência conforme registros do SIAFI. + - name: siafi_valor_pago description: > - Código da ocorrência de exclusão do servidor. - - name: dt_ocorr_exclusao + Valor pago no mês de referência conforme registros do SIAFI. + + - name: contratos_somatorio + description: > + Essa tabela contém somatórios dos valores de cronograma, fatura, empenho, liquidação e pagamento para cada contrato. + Facilita análises agregadas sobre a execução financeira total de cada contrato, com indicadores importantes + como o orçamento pendente de execução. + meta: + tags: + - gold + columns: + - name: contrato_id description: > - Data da ocorrência de exclusão do servidor. - - name: nome_ocorr_exclusao + Identificador único do contrato. + - name: total_cronograma description: > - Nome da ocorrência de exclusão do servidor. - - name: dt_uorg_lotacao + Soma de todos os valores programados no cronograma do contrato. + - name: total_faturas description: > - Data de movimentação para a UORG de lotação do servidor. - - name: cod_vale_transporte + Soma de todos os valores faturados (pagos e pendentes) para o contrato. + - name: total_saldo_disponivel description: > - Código do vale transporte do servidor. - - name: valor_vale_transporte + Diferença total entre valores programados e faturados, indicando o saldo contratual disponível. + - name: orcamento_a_executar description: > - Valor do vale transporte do servidor. - - name: dt_uorg_exercicio + Soma dos valores programados em meses onde ainda não houve faturamento, indicando o orçamento pendente de execução. + - name: total_empenhado description: > - Data de movimentação para a UORG de exercício do servidor. - - name: pontuacao_desempenho + Soma de todos os valores empenhados para o contrato segundo o SIAFI. + - name: total_liquidado description: > - Pontuação de desempenho do servidor. - - name: nome_escolaridade_principal + Soma de todos os valores liquidados para o contrato segundo o SIAFI. + - name: total_pago description: > - Nome da escolaridade do servidor (proveniente de dados_escolares). - - name: cod_escolaridade_principal + Soma de todos os valores pagos para o contrato segundo o SIAFI. + + ## View + - name: identificadores + description: > + Essa tabela contém os identificadores dos contratos, empenhos e faturas. + Consolida os números de empenho encontrados em diferentes fontes (contratos, faturas, empenhos) + e padroniza informações como processo e CNPJ/CPF para facilitar joins entre as tabelas. + Também cria um campo info_complementar que combina unidade gestora, modalidade e número de contrato/licitação. + columns: + - name: contrato_id description: > - Código da escolaridade do servidor (proveniente de dados_escolares). - - name: nome_titulacao_principal + Identificador único do contrato. + - name: categoria description: > - Nome da titulação do servidor (proveniente de dados_escolares). - - name: cod_titulacao_principal + Categoria do contrato ( Informática, Serviços, Mão de Obra). + - name: processo description: > - Código da titulação do servidor (proveniente de dados_escolares). - - name: dt_ultima_transacao_servidor + Número do processo administrativo do contrato, formatado para remover caracteres não numéricos. + - name: cnpj_cpf description: > - Data da última transação do servidor registrada no sistema SIAPE (proveniente de lista_servidores). - - name: bairro_uorg + CNPJ ou CPF do fornecedor, formatado para remover caracteres especiais como barras, pontos e hífens. + - name: info_complementar description: > - Bairro da UORG associada ao servidor (proveniente de dados_uorg). - - name: cep_uorg + Informação complementar construída a partir da unidade gestora, código de modalidade e número do contrato ou licitação. + - name: ne description: > - CEP da UORG associada ao servidor. - - name: codigo_matricula - description: > - Código de matrícula associado ao registro da UORG do servidor (contexto dados_uorg). - - name: codigo_municipio_uorg - description: > - Código do município da UORG associada ao servidor. - - name: codigo_orgao - description: > - Código do órgão da UORG associada ao servidor. - - name: codigo_orgao_uorg - description: > - Código da UORG (identificador da UORG em dados_uorg) associada ao servidor. - - name: email_uorg - description: > - Email da UORG associada ao servidor. - - name: tipo_endereco_uorg - description: > - Tipo de endereço da UORG associada ao servidor. - - name: logradouro_uorg - description: > - Logradouro da UORG associada ao servidor. - - name: nome_municipio_uorg - description: > - Nome do município da UORG associada ao servidor. - - name: nome_uorg - description: > - Nome da UORG associada ao servidor (conforme dados_uorg). - - name: telefone_uorg - description: > - Telefone da UORG associada ao servidor. - - name: numero_endereco_uorg - description: > - Número do endereço da UORG associada ao servidor. - - name: sigla_uorg - description: > - Sigla da UORG associada ao servidor (conforme dados_uorg). - - name: uf_uorg - description: > - UF da UORG associada ao servidor. - - name: complemento_endereco_uorg - description: > - Complemento do endereço da UORG associada ao servidor. - - name: fax_uorg - description: > - Fax da UORG associada ao servidor. + Número da nota de empenho relacionada ao contrato, combinando informações de contratos, empenhos e faturas. + + + + # Pessoas DBT + + # TED DBT + - name: pf_tesouro + description: > + Esta tabela contém registros de Programações Financeiras extraídas do SIAFI, detalhando informações como tipo de programação, data de execução e valor. + A tabela é atualizada diariamente e reflete as autorizações de movimentação financeira no âmbito da execução orçamentária federal. + meta: + tags: + - bronze + columns: + - name: emissao_mes + description: > + Mês de emissão da Programação Financeira. + - name: emissao_dia + description: > + Dia de emissão da Programação Financeira. + - name: ug_emitente + description: > + Código da Unidade Gestora responsável pela emissão da Programação Financeira. + - name: ug_emitente_descricao + description: > + Nome ou descrição da Unidade Gestora emitente da Programação Financeira. + - name: ug_favorecido + description: > + Código da Unidade Gestora favorecida pela Programação Financeira. + - name: ug_favorecido_descricao + description: > + Nome ou descrição da Unidade Gestora favorecida pela Programação Financeira. + - name: pf_evento + description: > + Código do evento contábil associado à Programação Financeira, representando a natureza da transação. + - name: pf_evento_descricao + description: > + Descrição do evento contábil vinculado à Programação Financeira. + - name: pf + description: > + Número identificador único da Programação Financeira. + - name: pf_inscricao + description: > + Número de inscrição da Programação Financeira, utilizado para controle e acompanhamento. + - name: pf_acao + description: > + Código da ação orçamentária associada à Programação Financeira. + - name: pf_acao_descricao + description: > + Descrição da ação orçamentária vinculada à Programação Financeira. + - name: pf_fonte_recursos + description: > + Código da fonte de recursos associada à Programação Financeira, indicando a origem dos recursos utilizados. + - name: pf_fonte_recursos_descricao + description: > + Descrição textual da fonte de recursos vinculada à Programação Financeira. + - name: pf_vinculacao_pagamento + description: > + Código da vinculação de pagamento associada à Programação Financeira. + - name: pf_vinculacao_pagamento_descricao + description: > + Descrição da vinculação de pagamento vinculada à Programação Financeira. + - name: pf_categoria_gasto + description: > + Código da categoria de gasto associada à Programação Financeira, conforme classificação orçamentária. + - name: pf_recurso + description: > + Código do recurso associado à Programação Financeira. + - name: pf_recurso_descricao + description: > + Descrição do recurso vinculado à Programação Financeira. + - name: doc_observacao + description: > + Observações adicionais relacionadas à Programação Financeira. + - name: pf_valor_linha + description: > + Valor monetário individual da linha da Programação Financeira. + data_tests: + - verificacao_tipagem: + nome_tabela: 'ted.pf_tesouro' + nome_coluna: 'emissao_dia' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'ted.pf_tesouro' + nome_coluna: 'pf_valor_linha' + tipo_esperado: 'numeric' + + - name: nc_tesouro + description: > + Esta tabela registra informações detalhadas sobre as Notas de Crédito emitidas no âmbito da execução orçamentária e financeira do governo federal. + As Notas de Crédito representam autorizações para a realização de despesas, sendo fundamentais para o controle e acompanhamento da execução orçamentária. + meta: + tags: + - bronze + columns: + - name: emissao_mes + description: > + Mês de emissão da Nota de Crédito. + - name: emissao_dia + description: > + Dia de emissão da Nota de Crédito. + - name: nc + description: > + Número identificador único da Nota de Crédito. + - name: nc_transferencia + description: > + Indicador de que a Nota de Crédito refere-se a uma transferência de recursos entre unidades gestoras. + - name: nc_fonte_recursos + description: > + Código da fonte de recursos associada à Nota de Crédito, indicando a origem dos recursos utilizados. + - name: nc_fonte_recursos_descricao + description: > + Descrição textual da fonte de recursos vinculada à Nota de Crédito. + - name: ptres + description: > + Código do Plano Interno de Trabalho (PTRES) relacionado à Nota de Crédito, utilizado para detalhar a alocação dos recursos. + - name: nc_evento + description: > + Código do evento contábil associado à Nota de Crédito, representando a natureza da transação. + - name: nc_evento_descricao + description: > + Descrição do evento contábil vinculado à Nota de Crédito. + - name: ug_responsavel + description: > + Código da Unidade Gestora responsável pela emissão da Nota de Crédito. + - name: ug_responsavel_descricao + description: > + Nome ou descrição da Unidade Gestora responsável pela emissão da Nota de Crédito. + - name: natureza_despesa + description: > + Código da natureza da despesa associada à Nota de Crédito, conforme classificação orçamentária. + - name: natureza_despesa_detalhada + description: > + Descrição detalhada da natureza da despesa vinculada à Nota de Crédito. + - name: plano_interno + description: > + Código do plano interno relacionado à Nota de Crédito, utilizado para controle interno da execução orçamentária. + - name: plano_detalhado_descricao1 + description: > + Primeira descrição detalhada do plano interno associado à Nota de Crédito. + - name: plano_detalhado_descricao2 + description: > + Segunda descrição detalhada do plano interno associado à Nota de Crédito. + - name: favorecido_doc + description: > + Documento de identificação (CPF ou CNPJ) do favorecido pela Nota de Crédito. + - name: favorecido_doc_descricao + description: > + Nome ou razão social do favorecido identificado na Nota de Crédito. + - name: nc_valor_linha + description: > + Valor monetário individual da linha da Nota de Crédito. + - name: movimento_liquido + description: > + Valor líquido do movimento financeiro associado à Nota de Crédito, após deduções ou acréscimos aplicáveis. + + - name: planos_acao + description: > + Esta tabela armazena informações detalhadas sobre os Planos de Ação vinculados a programas públicos. + Ela consolida dados sobre as unidades envolvidas na execução, prazos de vigência, valores, justificativas, + formas de execução e instrumentos utilizados. Serve como base para o acompanhamento, análise e auditoria + da implementação de ações públicas em diferentes formatos de execução. + meta: + tags: + - bronze + columns: + - name: id_plano_acao + description: > + Identificador único do Plano de Ação. + - name: id_programa + description: > + Identificador único do programa ao qual o Plano de Ação está vinculado. + - name: sigla_unidade_descentralizada + description: > + Sigla da unidade descentralizada responsável pelo Plano de Ação. + - name: unidade_descentralizada + description: > + Nome completo da unidade descentralizada responsável pelo Plano de Ação. + - name: sigla_unidade_responsavel_execucao + description: > + Sigla da unidade responsável pela execução do Plano de Ação. + - name: unidade_responsavel_execucao + description: > + Nome completo da unidade responsável pela execução do Plano de Ação. + - name: vl_total_plano_acao + description: > + Valor total previsto para execução do Plano de Ação. + - name: dt_inicio_vigencia + description: > + Data de início da vigência do Plano de Ação. + - name: dt_fim_vigencia + description: > + Data final da vigência do Plano de Ação. + - name: tx_objeto_plano_acao + description: > + Descrição do objeto do Plano de Ação, informando seu propósito e escopo. + - name: tx_justificativa_plano_acao + description: > + Justificativa para a elaboração e execução do Plano de Ação. + - name: in_forma_execucao_direta + description: > + Indicador booleano que sinaliza se a execução do plano ocorrerá de forma direta. + - name: in_forma_execucao_particulares + description: > + Indicador booleano que sinaliza se a execução contará com a participação de particulares. + - name: in_forma_execucao_descentralizada + description: > + Indicador booleano que sinaliza se a execução será realizada de forma descentralizada. + - name: tx_situacao_plano_acao + description: > + Situação atual do Plano de Ação ( em elaboração, aprovado, em execução, concluído). + - name: aa_ano_plano_acao + description: > + Ano de referência do Plano de Ação. + - name: vl_beneficiario_especifico + description: > + Valor destinado especificamente a beneficiários definidos no Plano de Ação. + - name: vl_chamamento_publico + description: > + Valor previsto para execução por meio de chamamento público. + - name: sq_instrumento + description: > + Código sequencial do instrumento jurídico ou administrativo associado ao Plano de Ação. + - name: aa_instrumento + description: > + Ano de referência do instrumento associado ao Plano de Ação. + + - name: nota_credito + description: > + Esta tabela contém informações sobre as Notas de Crédito emitidas no contexto dos Planos de Ação. + As notas de crédito representam movimentações financeiras entre unidades gestoras e gestões, sendo + vinculadas a um Plano de Ação específico. A tabela registra dados como número, data de emissão, + códigos das unidades e gestões envolvidas, além de observações e situação da nota. + meta: + tags: + - bronze + columns: + - name: id_nota + description: > + Identificador único da Nota de Crédito. + - name: id_plano_acao + description: > + Identificador único do Plano de Ação ao qual a Nota de Crédito está vinculada. + - name: tx_minuta_nota + description: > + Texto ou rascunho da minuta da Nota de Crédito. + - name: tx_numero_nota + description: > + Número da Nota de Crédito, utilizado para controle e rastreabilidade. + - name: dt_emissao_nota + description: > + Data e hora da emissão da Nota de Crédito. + - name: cd_gestao_emitente_nota + description: > + Código da gestão pública que emitiu a Nota de Crédito. + - name: cd_gestao_favorecida_nota + description: > + Código da gestão pública favorecida pela Nota de Crédito. + - name: tx_situacao_nota + description: > + Situação atual da Nota de Crédito ( emitida, cancelada, aprovada). + - name: cd_ug_emitente_nota + description: > + Código da Unidade Gestora que emitiu a Nota de Crédito. + - name: cd_ug_favorecida_nota + description: > + Código da Unidade Gestora favorecida pela Nota de Crédito. + - name: tx_observacao_nota + description: > + Observações adicionais registradas na Nota de Crédito, quando houver. - - name: afastamento_consolidado + - name: programacao_financeira description: > - Esta tabela consolida todos os registros de afastamentos, licenças e férias dos servidores, - unindo dados atuais (de 'dados_afastamento') com dados históricos (de 'afastamento_historico'). - Inclui detalhes sobre os períodos, tipos de ocorrência, amparo legal e informações financeiras relacionadas, como adiantamentos. - A coluna 'origem_dados' indica a fonte original de cada registro. + Esta tabela armazena os registros de Programações Financeiras vinculadas aos Planos de Ação. + A Programação Financeira representa o planejamento e execução da alocação de recursos entre unidades gestoras. + A tabela contempla informações como tipo, número, situação, unidade emitente e favorecida, + bem como observações e o documento hábil de recebimento. + meta: + tags: + - bronze columns: - - name: adiantamento_salario_ferias - description: > - Indica se houve adiantamento de salário referente ao período de férias. - - name: ano_exercicio - description: > - Ano de exercício a que o afastamento ou as férias se referem. - - name: dt_fim - description: > - Data de término do período de afastamento ou férias. - - name: dt_fim_aquisicao - description: > - Data final do período aquisitivo de férias. - - name: dt_ini - description: > - Data de início do período de afastamento ou férias. - - name: dt_inicio_aquisicao - description: > - Data inicial do período aquisitivo de férias. - - name: dt_inicio_ferias_interrompidas - description: > - Data de início de um período de férias que foi posteriormente interrompido. - - name: dias_restantes - description: > - Quantidade de dias restantes de um período de férias ou afastamento, usualmente após uma interrupção. - - name: gratificacao_natalina - description: > - Indicador se o afastamento está relacionado ao gozo ou adiantamento da gratificação natalina (13º salário). - - name: numero_parcela - description: > - Número da parcela, caso o afastamento ou férias seja dividido em múltiplos períodos. - - name: parcela_continuacao_interrupcao - description: > - Indicador se a parcela é uma continuação de um período anterior ou uma interrupção. - - name: parcelainterrompida - description: > - Indicador se a parcela de férias em questão foi interrompida. - - name: qtde_dias - description: > - Quantidade total de dias do afastamento ou do período de férias. - - name: cpf - description: > - CPF do servidor ao qual o registro de afastamento se refere. - - name: cod_diploma_afastamento - description: > - Código do diploma legal (lei, decreto, portaria) que fundamenta o afastamento. - - name: cod_ocorrencia - description: > - Código numérico ou alfanumérico que identifica o tipo de ocorrência (ex: tipo de licença, férias). - - name: dt_publicacao_afastamento - description: > - Data em que o ato de concessão do afastamento foi publicado oficialmente. - - name: desc_diploma_afastamento - description: > - Descrição textual do diploma legal que fundamenta o afastamento. - - name: desc_ocorrencia - description: > - Descrição textual do tipo de ocorrência (ex: 'FÉRIAS REGULAMENTARES', 'LICENÇA MÉDICA'). - - name: numero_diploma_afastamento - description: > - Número do diploma legal (lei, decreto, portaria) que fundamenta o afastamento. - - name: gr_matricula - description: > - Matrícula GR (Guia de Recolhimento) associada ao afastamento. Pode ser nulo para registros históricos. - - name: origem_dados - description: > - Indica a tabela de origem do registro, sendo 'dados_afastamento' para registros mais recentes - e 'afastamento_historico' para registros mais antigos. + - name: id_programacao + description: > + Identificador único da Programação Financeira. + - name: id_plano_acao + description: > + Identificador único do Plano de Ação ao qual a Programação Financeira está vinculada. + - name: tp_pf_tipo_programacao + description: > + Tipo de Programação Financeira ( ordinária, suplementar, especial). + - name: tx_minuta_programacao + description: > + Minuta ou rascunho da Programação Financeira. + - name: tx_numero_programacao + description: > + Número da Programação Financeira, utilizado para controle interno e rastreamento. + - name: tx_situacao_programacao + description: > + Situação atual da Programação Financeira ( em análise, aprovada, cancelada). + - name: tx_observacao_programacao + description: > + Observações complementares sobre a Programação Financeira, se houver. + - name: ug_emitente_programacao + description: > + Código ou nome da Unidade Gestora que emitiu a Programação Financeira. + - name: ug_favorecida_programacao + description: > + Código ou nome da Unidade Gestora favorecida pela Programação Financeira. + - name: dh_recebimento_programacao + description: > + Data e hora do recebimento do documento hábil relacionado à Programação Financeira. - ## Golds - - name: contratos_resumo + - name: programas description: > - Essa tabela contém um resumo dos contratos, informações contratuais como valor global e valor pago, - e situação atual, como em vigência ou pendente de baixa. - Facilita a análise gerencial dos contratos com indicadores chave como status de pagamento, tipo de fornecedor, - e identificação de contratos continuados (com vigência superior a dois anos e mais de uma parcela). + Esta tabela contém os registros dos Programas institucionais, que orientam e organizam os Planos de Ação + sob responsabilidade de diferentes unidades da administração pública. A tabela agrega dados como código, + nome, ano, situação, descrição, objetivos e informações relacionadas às autorizações e tipos de investimentos, + além de indicar critérios específicos como beneficiários e chamamentos públicos. + meta: + tags: + - bronze columns: - - name: contrato_id - description: > - Identificador único do contrato. - - name: fornecedor_cnpj_cpf - description: > - CNPJ ou CPF do fornecedor contratado, com formatação padronizada. - - name: numero - description: > - Número do contrato no sistema ComprasGov. - - name: categoria - description: > - Categoria do contrato (ex: Informática, Serviços, Mão de Obra). - - name: modalidade - description: > - Modalidade de licitação utilizada na contratação. - - name: tipo - description: > - Tipo de contrato (ex: Contrato, Empenho, Termo de Compromisso). - - name: situacao - description: > - Situação atual do contrato (Ativo ou Inativo). - - name: pendente_baixa - description: > - Indica se o contrato está pendente de baixa ('Sim' quando o valor pago for igual ao valor global, 'Não' caso contrário). - - name: fornecedor_nome - description: > - Nome ou razão social do fornecedor contratado. - - name: objeto - description: > - Descrição detalhada do objeto contratado. - - name: valor_global - description: > - Valor total do contrato após eventuais aditivos. - - name: despesas_pagas - description: > - Valor total já pago do contrato conforme registros do SIAFI. - - name: vigencia_inicio - description: > - Data de início da vigência do contrato. - - name: vigencia_fim - description: > - Data de término da vigência do contrato. - - name: num_parcelas - description: > - Número de parcelas para pagamento do contrato. - - name: fornecedor_tipo - description: > - Tipo do fornecedor, categorizado como 'Empresa do Exterior' para IDGENERICO. - - name: Unidade - description: > - Unidade gestora responsável pelo contrato, no formato "código - nome_resumido". - - name: continuado - description: > - Indica se o contrato é de prestação continuada ('Sim' quando a vigência for maior que 730 dias e tiver mais de uma parcela). + - name: id_programa + description: > + Identificador único do Programa. + - name: tx_codigo_programa + description: > + Código atribuído ao Programa para fins de controle e identificação institucional. + - name: aa_ano_programa + description: > + Ano de referência do Programa. + - name: tx_situacao_programa + description: > + Situação atual do Programa ( ativo, encerrado, em elaboração). + - name: tx_nome_programa + description: > + Nome oficial do Programa. + - name: sigla_unidade_descentralizadora + description: > + Sigla da unidade responsável pela descentralização dos recursos do Programa. + - name: unidade_descentralizadora + description: > + Nome completo da unidade descentralizadora responsável pelo Programa. + - name: sigla_unidade_responsavel_acompanhamento + description: > + Sigla da unidade responsável pelo acompanhamento do Programa. + - name: unidade_responsavel_acompanhamento + description: > + Nome completo da unidade responsável pelo acompanhamento do Programa. + - name: tx_nome_institucional_programa + description: > + Nome institucional utilizado oficialmente para identificar o Programa. + - name: tx_objetivo_programa + description: > + Objetivo principal do Programa, descrevendo seu propósito estratégico. + - name: tx_descricao_programa + description: > + Descrição detalhada do Programa, abrangendo seu escopo e diretrizes. + - name: in_grupo_investimento_obra + description: > + Indicador booleano que sinaliza se o Programa está vinculado a investimento em obras. + - name: in_grupo_investimento_servico + description: > + Indicador booleano que sinaliza se o Programa está vinculado a investimento em serviços. + - name: in_grupo_investimento_equipamento + description: > + Indicador booleano que sinaliza se o Programa está vinculado a investimento em equipamentos. + - name: in_autoriza_subdescentralizacao_outro + description: > + Indicador que informa se o Programa autoriza subdescentralização para outras unidades. + - name: in_autoriza_realizacao_despesas + description: > + Indicador que informa se o Programa autoriza a realização de despesas diretamente. + - name: in_autoriza_execucao_creditos_descentralizada + description: > + Indicador que informa se o Programa autoriza a execução descentralizada de créditos. + - name: in_beneficiario_especifico + description: > + Indicador booleano de existência de beneficiário específico no Programa. + - name: dt_recebimento_plano_beneficiario_inicio + description: > + Data de início para recebimento do plano de beneficiário específico vinculado ao Programa. + - name: dt_recebimento_plano_beneficiario_fim + description: > + Data final para recebimento do plano de beneficiário específico vinculado ao Programa. + - name: in_chamamento_publico + description: > + Indicador booleano de execução via chamamento público no âmbito do Programa. + - name: dt_recebimento_plano_chamamento_inicio + description: > + Data de início para recebimento do plano relacionado a chamamento público. + - name: dt_recebimento_plano_chamamento_fim + description: > + Data final para recebimento do plano relacionado a chamamento público. - - name: contratos_comparativo_mensal + - name: empenhos_plano_acao description: > - Essa tabela contém um comparativo mensal dos contratos, discriminando - os valores empenhados, liquidados e pagos do SIAFI, - programados e faturados do ComprasGov. - Permite a identificação de inconsistências entre os sistemas e o acompanhamento detalhado - da execução financeira mensal de cada contrato. + Esta tabela estabelece a relação entre os empenhos registrados no SIAFI e os respectivos Planos de Ação, + permitindo o rastreamento das despesas públicas desde a origem do crédito até sua execução. + meta: + tags: + - silver columns: - - name: contrato_id - description: > - Identificador único do contrato. - - name: mes_ref - description: > - Mês de referência da informação financeira, preenchido para todos os meses entre o início e fim do contrato. - - name: comprasgov_valor_cronograma - description: > - Valor programado para o mês conforme cronograma registrado no ComprasGov. - - name: comprasgov_valor_faturas - description: > - Soma dos valores das faturas (pagas e pendentes) no mês de referência conforme ComprasGov. - - name: comprasgov_saldo_contratual_disponivel - description: > - Diferença entre o valor programado e o valor faturado no mês, indicando o saldo disponível. - - name: siafi_valor_empenhado - description: > - Valor empenhado no mês de referência conforme registros do SIAFI. - - name: siafi_valor_liquidado - description: > - Valor liquidado no mês de referência conforme registros do SIAFI. - - name: siafi_valor_pago - description: > - Valor pago no mês de referência conforme registros do SIAFI. + - name: ne + description: > + Número da Nota de Empenho, extraído dos 12 últimos dígitos do campo `ne_ccor`, representando o identificador do empenho no SIAFI. + - name: num_transf + description: > + Número da transferência identificado na descrição da nota de empenho (`ne_ccor_descricao`), utilizado para vincular o empenho ao Plano de Ação correspondente. + - name: nc + description: > + Código da Nota de Crédito associado ao empenho, extraído da descrição da nota de empenho e formatado conforme padrão estabelecido. + - name: plano_acao + description: > + Identificador do Plano de Ação relacionado ao empenho, obtido a partir da correspondência com o número de transferência (`num_transf`). + - name: ne_ccor + description: > + Campo original contendo o código completo da Nota de Empenho, utilizado como base para extração de identificadores. + - name: ne_ccor_descricao + description: > + Descrição textual da Nota de Empenho, de onde são extraídos os números de transferência e códigos de nota de crédito para associação com os Planos de Ação. + - name: demais_colunas + description: > + Outras colunas provenientes da tabela `empenhos_tesouro`, contendo informações adicionais sobre os empenhos, como data de emissão, valor, unidade gestora, entre outras. - - name: contratos_somatorio + - name: nc_plano_acao description: > - Essa tabela contém somatórios dos valores de cronograma, fatura, empenho, liquidação e pagamento para cada contrato. - Facilita análises agregadas sobre a execução financeira total de cada contrato, com indicadores importantes - como o orçamento pendente de execução. + Esta tabela estabelece a relação entre as Notas de Crédito registradas no SIAFI e os respectivos Planos de Ação, + permitindo o rastreamento das movimentações financeiras desde a origem do crédito até sua aplicação. + meta: + tags: + - silver columns: - - name: contrato_id - description: > - Identificador único do contrato. - - name: total_cronograma - description: > - Soma de todos os valores programados no cronograma do contrato. - - name: total_faturas - description: > - Soma de todos os valores faturados (pagos e pendentes) para o contrato. - - name: total_saldo_disponivel - description: > - Diferença total entre valores programados e faturados, indicando o saldo contratual disponível. - - name: orcamento_a_executar - description: > - Soma dos valores programados em meses onde ainda não houve faturamento, indicando o orçamento pendente de execução. - - name: total_empenhado - description: > - Soma de todos os valores empenhados para o contrato segundo o SIAFI. - - name: total_liquidado - description: > - Soma de todos os valores liquidados para o contrato segundo o SIAFI. - - name: total_pago - description: > - Soma de todos os valores pagos para o contrato segundo o SIAFI. + - name: plano_acao + description: > + Identificador do Plano de Ação relacionado à Nota de Crédito, obtido a partir da correspondência com o número de transferência (`nc_transferencia`). + - name: emissao_dia + description: > + Dia de emissão da Nota de Crédito. + - name: num_transf + description: > + Número da transferência associado à Nota de Crédito, utilizado para vincular a movimentação financeira ao Plano de Ação correspondente. + - name: nc + description: > + Código da Nota de Crédito, extraído dos 12 últimos dígitos do campo `nc`, representando o identificador da nota no SIAFI. + - name: nc_fonte_recursos + description: > + Código da fonte de recursos associada à Nota de Crédito, indicando a origem dos recursos utilizados. + - name: ptres + description: > + Código do Plano Interno de Trabalho (PTRES) vinculado à Nota de Crédito, representando a ação orçamentária correspondente. + - name: ug_emitente + description: > + Código da Unidade Gestora emitente da Nota de Crédito, extraído dos 6 primeiros dígitos do campo `nc`. + - name: nc_natureza_despesa + description: > + Código da natureza da despesa associada à Nota de Crédito, conforme classificação orçamentária. + - name: nc_evento + description: > + Código do evento contábil associado à Nota de Crédito, representando a natureza da transação. + - name: nc_evento_descr + description: > + Descrição do evento contábil vinculado à Nota de Crédito. + - name: nc_valor + description: > + Valor monetário da Nota de Crédito, ajustado conforme o tipo de evento contábil. - ## View - - name: identificadores + - name: pf_plano_acao description: > - Essa tabela contém os identificadores dos contratos, empenhos e faturas. - Consolida os números de empenho encontrados em diferentes fontes (contratos, faturas, empenhos) - e padroniza informações como processo e CNPJ/CPF para facilitar joins entre as tabelas. - Também cria um campo info_complementar que combina unidade gestora, modalidade e número de contrato/licitação. + Esta tabela consolida as programações financeiras executadas no âmbito do SIAFI que estão relacionadas a Planos de Ação. + meta: + tags: + - silver columns: - - name: contrato_id - description: > - Identificador único do contrato. - - name: categoria - description: > - Categoria do contrato (ex: Informática, Serviços, Mão de Obra). - - name: processo - description: > - Número do processo administrativo do contrato, formatado para remover caracteres não numéricos. - - name: cnpj_cpf - description: > - CNPJ ou CPF do fornecedor, formatado para remover caracteres especiais como barras, pontos e hífens. - - name: info_complementar - description: > - Informação complementar construída a partir da unidade gestora, código de modalidade e número do contrato ou licitação. - - name: ne - description: > - Número da nota de empenho relacionada ao contrato, combinando informações de contratos, empenhos e faturas. + - name: pf + description: > + Número identificador da Programação Financeira, conforme registrado no SIAFI. + - name: num_transf + description: > + Número da transferência extraído do campo `pf_inscricao`, utilizado como chave alternativa para vinculação com Planos de Ação. + - name: emissao_mes + description: > + Mês da emissão da Programação Financeira, no formato numérico (1 a 12). + - name: emissao_dia + description: > + Dia da emissão da Programação Financeira, no formato numérico (1 a 31). + - name: ug_emitente + description: > + Código da Unidade Gestora responsável pela emissão da Programação Financeira. + - name: ug_favorecido + description: > + Código da Unidade Gestora favorecida pela Programação Financeira. + - name: pf_evento + description: > + Código do evento contábil associado à Programação Financeira, que descreve o tipo de movimentação registrada. + - name: pf_evento_descricao + description: > + Descrição do evento contábil da Programação Financeira, fornecendo contexto adicional sobre o tipo de operação realizada. + - name: pf_acao + description: > + Código da ação orçamentária extraído da descrição da programação financeira, representando a finalidade da despesa. + - name: pf_valor_linha + description: > + Valor programado para execução financeira, referente à linha específica da Programação Financeira. + - name: plano_acao + description: > + Identificador do Plano de Ação associado à Programação Financeira, determinado por correspondência direta com o sistema TransfereGov ou via número de transferência. - # SIAPE DBT - ## Bronze - - name: lista_uorgs + - name: ted_resumo_orcamentario description: > - Modelo bronze para dados de uorg. Inclui limpeza básica, tipagem, transformação de datas + Tabela de consolidação orçamentária e financeira por Plano de Ação, baseada em informações de valores firmados, orçamentos recebidos e devolvidos, empenhos, despesas e transferências financeiras. + Os dados são integrados de diferentes fontes (notas de crédito, empenhos e programações financeiras) para fornecer um resumo abrangente da execução orçamentária e financeira de transferências intergovernamentais. + meta: + tags: + - gold columns: - - name: codigo - description: > - codigo da uorg - - name: dt_ultima_transacao - description: > - data da última transação - - name: nome - description: > - nome + - name: plano_acao + description: > + Identificador único do Plano de Ação associado aos registros orçamentários e financeiros. + - name: num_transf + description: > + Número da transferência utilizado como chave de ligação entre diferentes fontes de dados. + - name: sigla_unidade_descentralizada + description: > + Sigla da Unidade Descentralizada responsável pelo Plano de Ação. + - name: ted_beneficiario_emitente + description: > + Identifica se a Unidade do Plano de Ação é a beneficiária ou a emitente dos recursos da TED (Transferência Voluntária). Valores possíveis: 'beneficiario', 'emitente' ou 'nao_indicado'. + - name: valor_firmado + description: > + Valor total originalmente acordado no Plano de Ação como meta de transferência de recursos. + - name: orcamento_recebido + description: > + Total de créditos orçamentários efetivamente recebidos por meio de Notas de Crédito. + - name: orcamento_devolvido + description: > + Total de créditos orçamentários devolvidos, identificado por eventos contábeis específicos (ex.: 300301, 300307). + - name: empenhado + description: > + Soma dos valores empenhados, ou seja, das despesas formalizadas no orçamento, excluindo anulações. + - name: empenho_anulado + description: > + Total de valores de empenhos anulados, ou seja, revertidos após sua emissão. + - name: despesas_pagas_exercicio + description: > + Total de despesas pagas no exercício corrente. + - name: despesas_pagas_rap + description: > + Total de despesas pagas com recursos de Restos a Pagar. + - name: restos_a_pagar + description: > + Valor total inscrito como Restos a Pagar, ou seja, despesas empenhadas e não pagas até o fim do exercício. + - name: despesas_liquidada + description: > + Soma das despesas liquidadas, indicando bens ou serviços efetivamente entregues. + - name: financeiro_recebido + description: > + Total de recursos financeiros recebidos via Programação Financeira (TED), considerando apenas ações classificadas como 'TRANSFERENCIA'. + - name: financeiro_devolvido + description: > + Total de recursos financeiros devolvidos, com ações classificadas como 'DEVOLUCAO'. + - name: financeiro_cancelado + description: > + Valor total de cancelamentos financeiros identificados na programação, com ação 'CANCELAMENTO'. - - name: lista_servidores + - name: num_transf_n_plano_acao description: > - Modelo bronze para dados de servidores. Inclui limpeza básica, tipagem, transformação de datas + View responsável por mapear o número de transferência (`num_transf`) ao respectivo Plano de Ação (`plano_acao`). + Utiliza dados de Notas de Crédito do TransfereGov e do SIAFI para realizar o cruzamento entre diferentes sistemas. columns: - - name: cod_uorg - description: > - codigo da uorg - - name: dt_ultima_transacao - description: > - data da última transação - - name: cpf - description: > - registro pessoa física + - name: num_transf + description: > + Número da transferência financeira obtido a partir dos registros do SIAFI (nota de crédito). Serve como chave para integrar informações entre diferentes fontes, como notas de crédito, programações financeiras e empenhos. + - name: plano_acao + description: > + Identificador único do Plano de Ação, conforme registrado no TransfereGov. É atribuído ao `num_transf` com base no cruzamento entre a nota de crédito (NC) e a unidade gestora emitente (UG). - - name: dados_uorg + # Bronze + - name: afastamento_historico description: > - Tabela bronze contendo dados de Unidades Organizacionais (UORG), - após limpeza básica, padronização de tipos e formatos a partir da fonte bruta. - Esta tabela serve como uma representação fiel e limpa dos dados de origem. + Tabela bronze que armazena o histórico de afastamentos dos servidores. + Contém informações detalhadas sobre cada período de afastamento, incluindo datas, tipo, e amparo legal. + Os dados são limpos e padronizados a partir da fonte original. + meta: + tags: + - bronze columns: - - name: bairro_uorg - description: > - Bairro do endereço da UORG. - data_type: varchar - - - name: cep_uorg - description: > - CEP (Código de Endereçamento Postal) da UORG, - contendo apenas dígitos (limpo). - data_type: varchar(8) - # tests: - # - dbt_utils.equal_length: - # value: 8 - # config: - # severity: warn - - - name: codigo_matricula - description: > - Código de matrícula associado à UORG. - Pode conter zeros à esquerda ou ser alfanumérico. - data_type: varchar - # tests: - # - not_null - - - name: codigo_municipio_uorg - description: > - Código do município da UORG. - data_type: varchar - - - name: codigo_orgao - description: > - Código do órgão ao qual a UORG pertence. - data_type: varchar - - - name: codigo_orgao_uorg - description: > - Código identificador único da UORG dentro do órgão. - data_type: varchar - # tests: - # - not_null - # - unique - - - name: email_uorg - description: > - Endereço de e-mail da UORG, padronizado para minúsculas. - data_type: varchar - # tests: - # - dbt_utils.expression_is_true: - # expression: > # Exemplo de como aplicar em expressões também - # email_uorg LIKE '%@%.%' - # config: - # severity: warn - - - name: tipo_endereco_uorg - description: > - Tipo ou descrição principal do endereço da UORG (ex: ENDERECO PRINCIPAL). - data_type: varchar - - - name: logradouro_uorg - description: > - Logradouro (rua, avenida, etc.) do endereço da UORG. - data_type: varchar - - - name: nome_municipio_uorg - description: > - Nome do município onde a UORG está localizada. - data_type: varchar - - - name: nome_uorg - description: > - Nome completo da Unidade Organizacional (UORG). - data_type: varchar - tests: - - not_null - - - name: telefone_uorg - description: > - Número de telefone da UORG, contendo apenas dígitos (limpo). - data_type: varchar - - - name: numero_endereco_uorg - description: > - Número do endereço da UORG. Pode conter 'S/N' para 'Sem Número'. - data_type: varchar - - - name: sigla_uorg - description: > - Sigla ou nome curto da UORG. - data_type: varchar - - - name: uf_uorg - description: > - Sigla da Unidade Federativa (Estado) da UORG, padronizada para maiúsculas. - data_type: varchar(2) - + - name: adiantamento_salario_ferias + description: "Indica se houve adiantamento de salário durante as férias." + - name: ano_exercicio + description: "Ano de exercício a que o afastamento se refere." + - name: dt_fim + description: "Data de término do afastamento." + - name: dt_fim_aquisicao + description: "Data final do período aquisitivo de férias." + - name: dt_ini + description: "Data de início do afastamento." + - name: dt_inicio_aquisicao + description: "Data inicial do período aquisitivo de férias." + - name: dt_inicio_ferias_interrompidas + description: "Data de início de férias que foram interrompidas." + - name: dias_restantes + description: "Dias restantes de afastamento." + - name: gratificacao_natalina + description: "Indica se o afastamento está relacionado à gratificação natalina." + - name: numero_parcela + description: "Número da parcela do afastamento." + - name: parcela_continuacao_interrupcao + description: "Indica se a parcela é uma continuação ou interrupção." + - name: parcelainterrompida + description: "Indica se a parcela foi interrompida." + - name: qtde_dias + description: "Quantidade de dias do afastamento." - name: cpf - description: > - CPF associado à UORG, contendo apenas os 11 dígitos (limpo). - data_type: varchar(11) - # tests: - # - dbt_utils.equal_length: - # value: 11 - # config: - # severity: warn - - - name: complemento_endereco_uorg - description: > - Complemento do endereço da UORG (ex: Sala, Bloco, Apartamento). - data_type: varchar + description: "CPF do servidor." + - name: cod_diploma_afastamento + description: "Código do diploma legal do afastamento." + - name: cod_ocorrencia + description: "Código da ocorrência do afastamento." + - name: dt_publicacao_afastamento + description: "Data de publicação do afastamento." + - name: desc_diploma_afastamento + description: "Descrição do diploma legal do afastamento." + - name: desc_ocorrencia + description: "Descrição da ocorrência do afastamento." + - name: numero_diploma_afastamento + description: "Número do diploma legal do afastamento." - - name: fax_uorg - description: > - Número de fax da UORG, contendo apenas dígitos (limpo). - data_type: varchar - - name: dados_pessoais + - name: cargos_funcoes description: > - Tabela bronze contendo dados pessoais de indivíduos, após limpeza básica, - padronização de tipos e formatos a partir da fonte bruta. + Tabela bronze que representa as funções e cargos extraídos do sistema SIORG. + Ela contém informações sobre o código e nome do cargo/função, nível hierárquico, + categoria funcional, regras de autoridade, além de dados normativos (ato normativo) + e as diversas denominações associadas a cada cargo/função. + Os dados são expandidos a partir de arrays JSON para facilitar a análise. + meta: + tags: + - bronze columns: - - name: cod_cor - description: > - Código da cor/raça do indivíduo. - data_type: varchar - # tests: - # - not_null - - - name: cod_estado_civil - description: > - Código do estado civil do indivíduo. - data_type: varchar + - name: codigotipo + description: "Código que representa o tipo do cargo ou função." - - name: cod_nacionalidade - description: > - Código da nacionalidade do indivíduo. - data_type: varchar + - name: nome + description: "Nome completo do cargo ou função." - - name: cod_sexo - description: > - Código do sexo do indivíduo (ex: M, F). - data_type: varchar(1) + - name: sigla + description: "Sigla identificadora do cargo ou função." - - name: dt_nascimento - description: > - Data de nascimento do indivíduo, convertida para o tipo DATE. - data_type: date - # tests: - # - not_null + - name: codigocargofuncao + description: "Código único que identifica o cargo ou função." - - name: grupo_sanguineo - description: > - Grupo sanguíneo e fator RH do indivíduo (ex: A +, O -). - data_type: varchar + - name: categoria + description: "Categoria funcional do cargo, como direção, assessoramento, etc." - - name: nome_pessoa - description: > - Nome completo do indivíduo. - data_type: varchar - # tests: - # - not_null + - name: nivel + description: "Nível hierárquico do cargo ou função." - - name: nome_cor - description: > - Nome descritivo da cor/raça do indivíduo. - data_type: varchar + - name: atonormativo__tipoato + description: "Tipo do ato normativo que criou ou regulamenta o cargo/função." - - name: nome_estado_civil - description: > - Nome descritivo do estado civil do indivíduo. - data_type: varchar + - name: atonormativo__codigounidade + description: "Código da unidade responsável pelo ato normativo." - - name: nome_mae - description: > - Nome completo da mãe do indivíduo. - data_type: varchar + - name: atonormativo__numero + description: "Número do ato normativo relacionado ao cargo ou função." - - name: nome_municipio_nascimento - description: > - Nome do município de nascimento do indivíduo. - data_type: varchar + - name: atonormativo__dataassinatura + description: "Data em que o ato normativo foi assinado." - - name: nome_nacionalidade - description: > - Nome descritivo da nacionalidade do indivíduo (ex: BRASILEIRO NATO). - data_type: varchar + - name: atonormativo__datapublicacao + description: "Data de publicação do ato normativo no diário oficial." - - name: nome_pai - description: > - Nome completo do pai do indivíduo. 'NAO DECLARADO' é convertido para NULL. - data_type: varchar + - name: atonormativo__datavigencia + description: "Data em que o ato normativo passou a vigorar." - - name: nome_sexo - description: > - Nome descritivo do sexo do indivíduo (ex: MASCULINO, FEMININO). - data_type: varchar + - name: atonormativo__ementa + description: "Ementa ou resumo descritivo do ato normativo." - - name: num_pispasep - description: > - Número do PIS/PASEP do indivíduo, contendo apenas dígitos. - data_type: varchar # Para preservar zeros à esquerda e pelo tamanho - # tests: - # - dbt_utils.equal_length: - # value: 11 # PIS/PASEP tem 11 dígitos + - name: atonormativo__url + description: "URL de acesso ao conteúdo completo do ato normativo." - - name: uf_nascimento - description: > - Sigla da Unidade Federativa (Estado) de nascimento, padronizada para maiúsculas. - data_type: varchar(2) - # tests: - # - dbt_utils.equal_length: - # value: 2 + - name: atonormativo__codigotipo + description: "Código que representa o tipo de ato normativo." - - name: cpf - description: > - CPF do indivíduo, contendo apenas os 11 dígitos. - data_type: varchar(11) - # tests: - # - not_null - # - unique - # - dbt_utils.equal_length: - # value: 11 + - name: atonormativo__siglatipo + description: "Sigla que representa o tipo de ato normativo." - - name: cod_deficiencia_fisica - description: > - Código da deficiência física, se houver. - data_type: varchar + - name: denominacao_codigo + description: "Código individual da denominação expandida a partir do array JSON." - - name: nome_deficiencia_fisica - description: > - Nome da deficiência física, se houver. - data_type: varchar + - name: denominacao_descricao + description: "Descrição da denominação expandida para o cargo/função." - - name: dt_chegada_brasil - description: > - Data de chegada ao Brasil (para estrangeiros), convertida para o tipo DATE. - data_type: date - - name: nome_pais_origem - description: > - Nome do país de origem (para estrangeiros). - data_type: varchar - - - name: dados_pa + - name: dados_afastamento description: > - Tabela bronze contendo dados de beneficiários de pensões e informações relacionadas, - após limpeza básica e padronização de formatos a partir da fonte bruta. - + Tabela bronze com dados atuais de afastamentos dos servidores. + meta: + tags: + - bronze columns: - - name: agencia_beneficiario - description: > - Código da agência bancária do beneficiário, contendo apenas dígitos. - data_type: varchar - # tests: - # - not_null - - - name: banco_beneficiario - description: > - Código do banco do beneficiário. - data_type: varchar - # tests: - # - not_null - - - name: cod_orgao - description: > - Código do órgão pagador. - data_type: varchar - # tests: - # - not_null - - - name: conta_beneficiario - description: > - Número da conta bancária do beneficiário, limpo e padronizado para maiúsculas - data_type: varchar - # tests: - # - not_null - - - name: cpf_beneficiario - description: > - CPF do beneficiário, contendo apenas dígitos. - Pode ter comprimento variável se a fonte não for padronizada para 11 dígitos. - data_type: varchar - # tests: - # - not_null + - name: adiantamento_salario_ferias + description: "Indica se houve adiantamento de salário durante as férias." + - name: ano_exercicio + description: "Ano de exercício a que o afastamento se refere." + - name: cod_diploma_afastamento + description: "Código do diploma legal do afastamento." + - name: cod_ocorrencia + description: "Código da ocorrência do afastamento." + - name: desc_diploma_afastamento + description: "Descrição do diploma legal do afastamento." + - name: desc_ocorrencia + description: "Descrição da ocorrência do afastamento." + - name: dt_fim + description: "Data de término do afastamento." + - name: dt_ini + description: "Data de início do afastamento." + - name: dt_inicio_aquisicao + description: "Data inicial do período aquisitivo de férias." + - name: dt_publicacao_afastamento + description: "Data de publicação do afastamento." + - name: dias_restantes + description: "Dias restantes de afastamento." + - name: gratificacao_natalina + description: "Indica se o afastamento está relacionado à gratificação natalina." + - name: gr_matricula + description: "Matrícula GR." + - name: numero_diploma_afastamento + description: "Número do diploma legal do afastamento." + - name: numero_parcela + description: "Número da parcela do afastamento." + - name: parcela_continuacao_interrupcao + description: "Indica se a parcela é uma continuação ou interrupção." + - name: qtde_dias + description: "Quantidade de dias do afastamento." + - name: cpf + description: "CPF do servidor." + - name: dt_fim_aquisicao + description: "Data final do período aquisitivo de férias." - - name: matricula_servidor - description: > - Matrícula do servidor ao qual a pensão está vinculada. - data_type: varchar - # tests: - # - not_null - - name: nome_beneficiario - description: > - Nome completo do beneficiário da pensão. - data_type: varchar - # tests: - # - not_null + - name: dados_curriculo + description: > + Tabela bronze que representa dados curriculares e experiências profissionais dos servidores públicos. + Inclui informações sobre cursos realizados, instituições de ensino, tempo de experiência, projetos desenvolvidos e vínculos com órgãos ou empresas. + As datas foram padronizadas para o primeiro dia do mês (`dt_mes_`) e os dados textuais foram limpos de caracteres inválidos. + meta: + tags: + - bronze + columns: + - name: cpf + description: "CPF do servidor, formatado apenas com números." - - name: valor_ultima_pensao - description: > - Valor da última pensão registrada, como string. - data_type: varchar + - name: ident_unica + description: "Identificador único do servidor no sistema, usado para correlacionar registros." - - name: cpf_servidor - description: > - CPF do servidor ao qual a pensão está vinculada, contendo apenas os 11 dígitos. - data_type: varchar(11) - # tests: - # - not_null - # - dbt_utils.equal_length: - # value: 11 + - name: codigo_experiencia + description: "Código associado ao tipo de experiência do servidor ( 1 para formação, 2 para experiência profissional)." - - name: cod_vinculo_servidor - description: > - Código do tipo de vínculo do alimentado com o servidor. - data_type: varchar + - name: cod_curso + description: "Código do curso realizado, se disponível." - - name: nome_alimentado - description: > - Nome da pessoa alimentada (dependente/beneficiário da pensão, se diferente do beneficiário principal). - data_type: varchar + - name: nome_curso + description: "Nome do curso de formação acadêmica ou técnica. Exemplo: 'Ciências Contábeis', 'Administração'." - - name: nome_vinculo_servidor - description: > - Nome descritivo do tipo de vínculo do alimentado com o servidor (ex: FILHO(A), EX-CONJUGE). - data_type: varchar + - name: dt_mes_conclusao + description: "Data de conclusão do curso, normalizada para o primeiro dia do mês. Exemplo: '1991-12-01'." - - name: dados_servidores - description: > - Tabela bronze contendo dados funcionais e de vínculo de servidores, - após limpeza básica e padronização de formatos a partir da fonte bruta. - columns: - - name: cod_atividade_funcao - description: > - Código da atividade da função. - data_type: varchar - - name: cod_funcao - description: > - Código da função exercida. - data_type: varchar - - name: cod_jornada - description: > - Código da jornada de trabalho. - data_type: varchar - - name: cod_ocorr_ingresso_orgao - description: > - Código da ocorrência de ingresso no órgão. - data_type: varchar - - name: cod_ocorr_ingresso_serv_publico - description: > - Código da ocorrência de ingresso no serviço público. - data_type: varchar - - name: cod_orgao - description: > - Código do órgão. - data_type: varchar - - name: cod_padrao - description: > - Código do padrão de vencimento. - data_type: varchar - - name: cod_situacao_funcional - description: > - Código da situação funcional. - data_type: varchar - - name: cod_uorg_exercicio - description: > - Código da Unidade Organizacional de exercício. - data_type: varchar - - name: cod_upag - description: > - Código da Unidade Pagadora (UPAG). - data_type: varchar - - name: cod_orgao_origem - description: > - Código do órgão de origem do servidor. - data_type: varchar - - name: cpf_chefia_imediata - description: > - CPF da chefia imediata, limpo (apenas dígitos). - data_type: varchar(11) - - name: dt_exercicio_no_orgao - description: > - Data de início do exercício no órgão. - data_type: date - - name: dt_fim_vale_ar - description: > - Data final de validade do vale AR (Alimentação/Refeição). - data_type: date - - name: dt_ingresso_funcao - description: > - Data de ingresso na função atual. - data_type: date - - name: dt_ocorr_ingresso_orgao - description: > - Data da ocorrência de ingresso no órgão. - data_type: date - - name: dt_ocorr_ingresso_serv_publico - description: > - Data da ocorrência de ingresso no serviço público. - data_type: date - - name: email_chefia_imediata - description: > - E-mail da chefia imediata. - data_type: varchar - - name: email_institucional - description: > - E-mail institucional do servidor. - data_type: varchar - - name: email_servidor - description: > - E-mail pessoal do servidor. - data_type: varchar - - name: ident_unica - description: > - Identificador único do servidor. - data_type: varchar - - name: matricula_siape - description: > - Matrícula SIAPE do servidor. - data_type: varchar - # tests: - # - not_null - # - unique - - name: modalidade_pgd - description: > - Modalidade do Programa de Gestão de Desempenho (PGD). - data_type: varchar - - name: nome_atividade_funcao - description: > - Nome da atividade da função. - data_type: varchar - - name: nome_chefe_uorg - description: > - Nome da chefia da UORG. - data_type: varchar - - name: nome_funcao - description: > - Nome da função exercida. - data_type: varchar - - name: nome_jornada - description: > - Nome descritivo da jornada de trabalho. - data_type: varchar - - name: nome_ocorr_ingresso_orgao - description: > - Nome descritivo da ocorrência de ingresso no órgão. - data_type: varchar - - name: nome_ocorr_ingresso_serv_publico - description: > - Nome descritivo da ocorrência de ingresso no serviço público. - data_type: varchar - - name: nome_orgao - description: > - Nome do órgão. - data_type: varchar - - name: nome_regime_juridico - description: > - Nome do regime jurídico do servidor. - data_type: varchar - - name: nome_situacao_funcional - description: > - Nome descritivo da situação funcional (ex: ATIVO PERMANENTE, APOSENTADO). - data_type: varchar - - name: nome_uorg_exercicio - description: > - Nome da Unidade Organizacional de exercício. - data_type: varchar - - name: nome_upag - description: > - Nome da Unidade Pagadora (UPAG). - data_type: varchar - - name: participa_pgd - description: > - Indica se o servidor participa do PGD (ex: sim, não). - data_type: varchar - - name: percentual_ts - description: > - Percentual de Tempo de Serviço (TS), como valor numérico (ex: 12% = 0.12) - data_type: numeric - - name: sigla_orgao - description: > - Sigla do órgão. - data_type: varchar - - name: sigla_orgao_origem - description: > - Sigla do órgão de origem. - data_type: varchar - - name: sigla_regime_juridico - description: > - Sigla do regime jurídico. - data_type: varchar - - name: sigla_uorg_exercicio - description: > - Sigla da UORG de exercício. - data_type: varchar - - name: sigla_upag - description: > - Sigla da UPAG. - data_type: varchar - - name: cpf - description: > - CPF do servidor, contendo apenas os 11 dígitos. - data_type: varchar(11) - # tests: - # - not_null - # - dbt_utils.equal_length: - # value: 11 - - name: cod_cargo - description: > - Código do cargo do servidor. - data_type: varchar - - name: cod_classe - description: > - Código da classe do cargo. - data_type: varchar - - name: cod_ocorr_aposentadoria - description: > - Código da ocorrência de aposentadoria. - data_type: varchar - - name: dt_ini_vale_ar - description: > - Data inicial de validade do vale AR. - data_type: date - - name: dt_ocorr_aposentadoria - description: > - Data da ocorrência de aposentadoria. - data_type: date - - name: nome_cargo - description: > - Nome do cargo do servidor. - data_type: varchar - - name: nome_classe - description: > - Nome da classe do cargo. - data_type: varchar - - name: nome_ocorr_aposentadoria - description: > - Nome descritivo da ocorrência de aposentadoria. - data_type: varchar - - name: sigla_nivel_cargo - description: > - Sigla do nível do cargo (ex: NS, NI). - data_type: varchar - - name: tipo_vale_ar - description: > - Tipo do vale AR (ex: ALIMENTACAO, REFEICAO). - data_type: varchar - - name: cod_ocorr_isencao_ir - description: > - Código da ocorrência de isenção de Imposto de Renda. - data_type: varchar - - name: dt_ini_ocorr_isencao_ir - description: > - Data inicial da ocorrência de isenção de IR. - data_type: date - - name: nome_ocorr_isencao_ir - description: > - Nome descritivo da ocorrência de isenção de IR. - data_type: varchar - - name: cod_uorg_lotacao - description: > - Código da Unidade Organizacional de lotação. - data_type: varchar - - name: nome_uorg_lotacao - description: > - Nome da Unidade Organizacional de lotação. - data_type: varchar - - name: sigla_uorg_lotacao - description: > - Sigla da UORG de lotação. - data_type: varchar - - name: dt_fim_ocorr_isencao_ir - description: > - Data final da ocorrência de isenção de IR. - data_type: date - - name: cod_ocorr_exclusao - description: > - Código da ocorrência de exclusão. - data_type: varchar - - name: dt_ocorr_exclusao - description: > - Data da ocorrência de exclusão. - data_type: date - - name: nome_ocorr_exclusao - description: > - Nome descritivo da ocorrência de exclusão. - data_type: varchar - - name: dt_uorg_lotacao - description: > - Data de lotação na UORG. - data_type: date - - name: cod_vale_transporte - description: > - Código do vale transporte. - data_type: varchar - - name: valor_vale_transporte - description: > - Valor do vale transporte (como string). - data_type: numeric - - name: dt_uorg_exercicio - description: > - Data de exercício na UORG. - data_type: date - - name: pontuacao_desempenho + - name: nome_instituicao + description: "Nome da instituição onde o curso foi realizado. Exemplo: 'UDF', 'Universidade Católica de Brasília'." + + - name: nome_area_experiencia description: > - Pontuação de desempenho (mantido como string). - data_type: varchar + Grau ou situação da experiência/capacitação. Pode indicar o nível ou status como: + 'Concluído', 'Intermediário', 'Básico', 'Curso', 'Avançado'." + + - name: carga_horaria + description: "Carga horária do curso, se disponível. Armazenado como texto." + + - name: nome_cargo + description: "Nome do cargo ou função exercida durante a experiência profissional." + + - name: dt_mes_inicio + description: "Data de início da experiência profissional, no formato 'YYYY-MM-01'." + + - name: nome_orgao_empresa + description: "Nome da organização, empresa ou órgão público em que a experiência foi realizada." + + - name: dt_mes_fim + description: "Data de término da experiência profissional, normalizada para o primeiro dia do mês." + + - name: descricao_projeto + description: "Descrição de projetos relevantes associados à experiência." + + - name: informacoes_adicionais + description: "Informações complementares que detalham a experiência ou formação." + + - name: tipo_descricao + description: "Tipo da descrição curricular. Pode indicar se o registro refere-se a curso, experiência, projeto etc." + - - name: dados_financeiros + - name: dados_dependentes description: > - Tabela bronze contendo dados de rubricas financeiras ou de pagamento, - após limpeza básica e padronização de formatos + Tabela bronze que contém informações sobre os dependentes dos servidores públicos. + Inclui grau de parentesco, condições de dependência, benefícios associados e o período em que a dependência esteve ativa. + Os dados foram tratados para remover valores inválidos como 'NaN' e '00000000', e datas foram convertidas para o formato `DATE`. + meta: + tags: + - bronze columns: - - name: cod_rubrica + - name: cod_condicao description: > - Código da rubrica. - data_type: varchar - # tests: - # - not_null + Código que representa a condição da dependência ( dependente econômico, legal, etc.). + Usado para classificar o tipo de vínculo do dependente com o servidor. - - name: indicador_rd + - name: cod_grau_parentesco description: > - Indicador de Rendimento/Desconto (ex: D). - data_type: varchar + Código que indica o grau de parentesco do dependente com o servidor ( filho, cônjuge, pai, etc.). - - name: nome_rubrica - description: > - Nome descritivo da rubrica. - data_type: varchar + - name: cod_orgao + description: "Código do órgão ao qual o servidor está vinculado." - - name: numero_sequencia - description: > - Número sequencial associado à rubrica. - data_type: varchar + - name: cpf + description: "CPF do servidor titular da matrícula, contendo apenas números." - - name: valor_rubrica - description: > - Valor monetário associado à rubrica, convertido para NUMERIC. - data_type: numeric - # tests: - # - not_null + - name: matricula + description: "Número da matrícula funcional do servidor ao qual o dependente está associado." - - name: data_anomes_rubrica - description: > - Mês e ano da rubrica, convertido para DATE (representa o 1º dia do mês). - data_type: date + - name: nome_dependente + description: "Nome completo do dependente." - - name: prazo_rubrica + - name: nome_condicao description: > - Prazo associado à rubrica (ex: 001). - data_type: varchar + Descrição da condição do dependente ( 'Dependente para IR', 'Dependente para Plano de Saúde'). - - name: mes_ano_pagamento + - name: nome_grau_parentesco description: > - Mês e ano do pagamento, convertido para DATE (representa o 1º dia do mês). - data_type: date + Descrição textual do grau de parentesco entre o servidor e o dependente ( 'Filho', 'Cônjuge'). - - name: cpf + - name: cod_beneficio + description: "Código que representa o tipo de benefício relacionado ao dependente, se houver." + + - name: dt_fim description: > - CPF do indivíduo associado, contendo apenas os 11 dígitos. - data_type: varchar(11) - # tests: - # - not_null - # - dbt_utils.equal_length: - # value: 11 + Data de término da condição de dependência, no formato 'DDMMYYYY', convertida para DATE. + Pode ser nula se a dependência ainda estiver ativa. - - name: indicador_mov_supl + - name: dt_inicio description: > - Indicador de movimento suplementar. - data_type: varchar + Data de início da condição de dependência, no formato 'DDMMYYYY', convertida para DATE. - - name: periodo_rubrica + - name: nome_beneficio description: > - Período da rubrica. - data_type: varchar - + Nome do benefício associado ao dependente ( auxílio-saúde, plano de assistência etc.). + + - name: dados_escolares description: > - Tabela bronze contendo dados acadêmicos e de formação de indivíduos, - após limpeza básica e padronização de formatos a partir da fonte bruta. + Tabela bronze que armazena dados sobre a formação educacional dos servidores públicos. + Inclui informações sobre escolaridade, titulação e cursos associados às matrículas funcionais. + Os dados foram limpos para remover valores vazios e o CPF foi padronizado contendo apenas dígitos. + meta: + tags: + - bronze columns: - name: cod_curso - description: > - Código do curso. - data_type: varchar + description: "Código identificador do curso realizado pelo servidor." - name: nome_curso - description: > - Nome do curso. - data_type: varchar + description: "Nome do curso relacionado à formação do servidor." - name: cod_matricula - description: > - Código da matrícula associada ao curso ou indivíduo. - data_type: varchar - # tests: - # - not_null # Se for uma chave + description: "Código da matrícula funcional do servidor, vinculado ao curso." - name: cod_orgao - description: > - Código do órgão (possivelmente da instituição de ensino ou do servidor). - data_type: varchar + description: "Código do órgão público ao qual o servidor está vinculado." - name: cod_titulacao - description: > - Código da titulação obtida (ex: Doutorado, Graduação). - data_type: varchar + description: "Código que representa a titulação do servidor ( especialização, mestrado, doutorado)." - name: nome_titulacao - description: > - Nome descritivo da titulação. - data_type: varchar + description: "Descrição textual da titulação ( 'Mestrado', 'Doutorado')." - name: cod_escolaridade - description: > - Código do nível de escolaridade. - data_type: varchar + description: "Código do nível de escolaridade ( ensino médio, superior, técnico)." - name: nome_escolaridade - description: > - Nome descritivo do nível de escolaridade. - data_type: varchar + description: "Descrição do nível de escolaridade do servidor." - name: cpf - description: > - CPF do indivíduo, contendo apenas os 11 dígitos. - data_type: varchar(11) - # tests: - # - not_null - # - dbt_utils.equal_length: - # value: 11 + description: "CPF do servidor, contendo apenas números, utilizado para identificação única." - - name: dados_dependentes - description: > - Tabela bronze contendo dados de dependentes e benefícios associados, - após limpeza básica e padronização de formatos a partir da fonte bruta. + + - name: dados_financeiros + description: > + Tabela bronze que armazena dados financeiros associados a servidores públicos. + Contém informações detalhadas sobre rubricas (proventos e descontos), valores pagos, + períodos de referência e data de pagamento. Os dados passam por limpeza e transformação + de formatos monetários e temporais para garantir consistência e padronização. + meta: + tags: + - bronze columns: - - name: cod_condicao - description: > - Código da condição do dependente. - data_type: varchar + - name: cod_rubrica + description: "Código da rubrica financeira (provento ou desconto) referente ao pagamento do servidor." - - name: cod_grau_parentesco - description: > - Código do grau de parentesco do dependente. - data_type: varchar + - name: indicador_rd + description: "Indicador que diferencia se a rubrica é de receita (R) ou despesa (D)." - - name: cod_orgao - description: > - Código do órgão do servidor principal. - data_type: varchar + - name: nome_rubrica + description: "Nome descritivo da rubrica, como 'Salário Base', 'Auxílio Alimentação', etc." - - name: cpf - description: > - CPF do dependente, contendo apenas os 11 dígitos (se aplicável). - data_type: varchar(11) - # tests: - # - dbt_utils.equal_length: - # value: 11 - # config: - # severity: warn + - name: numero_sequencia + description: "Número sequencial que identifica a ordem da rubrica na folha de pagamento." - - name: matricula + - name: valor_rubrica description: > - Matrícula do servidor principal. - data_type: varchar - # tests: - # - not_null + Valor monetário da rubrica, convertido de string para tipo numérico. + Foi realizada limpeza dos pontos e substituição da vírgula decimal por ponto. - - name: nome_dependente + - name: data_anomes_rubrica description: > - Nome completo do dependente. - data_type: varchar - # tests: - # - not_null + Data correspondente ao mês e ano de referência da rubrica, convertida do formato textual ( 'JAN2020') para uma data no formato YYYY-MM-DD + com o dia fixado em 01. - - name: nome_condicao - description: > - Nome descritivo da condição do dependente. - data_type: varchar + - name: prazo_rubrica + description: "Informação de prazo da rubrica (campo opcional e com dados variados, pode indicar vencimento ou parcelamento)." - - name: nome_grau_parentesco + - name: mes_ano_pagamento description: > - Nome descritivo do grau de parentesco do dependente. - data_type: varchar + Data correspondente ao mês e ano de efetivação do pagamento, convertida do formato textual ( 'JAN2020') para data com o dia fixado em 01. - - name: cod_beneficio + - name: cpf description: > - Código do benefício associado ao dependente. - data_type: varchar + CPF do servidor, padronizado contendo apenas dígitos. Utilizado para vinculação com outras informações do servidor. - - name: dt_fim + - name: indicador_mov_supl description: > - Data final do benefício ou da condição, convertida para DATE. - data_type: date + Indicador que mostra se a movimentação financeira se refere a um pagamento suplementar ou retroativo (campo técnico da folha). - - name: dt_inicio + - name: periodo_rubrica description: > - Data inicial do benefício ou da condição, convertida para DATE. - data_type: date + Período a que se refere a rubrica, podendo representar um agrupamento ou classificação contábil adicional." - - name: nome_beneficio - description: > - Nome descritivo do benefício. - data_type: varchar - - name: dados_curriculo + - name: dados_funcionais description: > - Tabela bronze contendo dados de experiência - após limpeza básica e padronização de formatos a partir da fonte bruta. - + Tabela bronze que armazena os dados funcionais dos servidores, contendo informações + sobre cargos, funções, regimes jurídicos, ocorrências de ingresso e aposentadoria, e dados + de unidades organizacionais (lotação e exercício). As datas são padronizadas, os CPFs higienizados + e os valores tratados para garantir integridade e legibilidade dos dados. + meta: + tags: + - bronze columns: - - name: cpf - description: > - CPF do indivíduo, contendo apenas os 11 dígitos. - data_type: varchar(11) - # tests: - # - not_null + - name: cod_atividade_funcao + description: "Código da atividade ou função exercida pelo servidor." - - name: ident_unica - description: > - Identificador único associado. - data_type: varchar + - name: cod_funcao + description: "Código identificador da função do servidor." - - name: codigo_experiencia - description: > - Código geral da experiência/qualificação. - data_type: varchar + - name: cod_jornada + description: "Código da jornada de trabalho do servidor." - - name: cod_curso - description: > - Código do curso, se aplicável. - data_type: varchar + - name: cod_ocorr_ingresso_orgao + description: "Código da ocorrência referente ao ingresso no órgão." - - name: nome_curso - description: > - Nome do curso, se aplicável. - data_type: varchar + - name: cod_ocorr_ingresso_serv_publico + description: "Código da ocorrência de ingresso no serviço público federal." - - name: dt_mes_conclusao - description: > - Data de conclusão (formato YYYYMM), convertida para DATE (1º dia do mês). - data_type: date + - name: cod_orgao + description: "Código do órgão atual de lotação ou exercício do servidor." - - name: nome_instituicao - description: > - Nome da instituição de ensino ou empresa. - data_type: varchar + - name: cod_padrao + description: "Código do padrão do cargo ocupado." - - name: nome_area_experiencia - description: > - Nome da área ou tipo de experiência (ex: Licitação, Auditoria). - data_type: varchar + - name: cod_situacao_funcional + description: "Código representando a situação funcional atual do servidor (ativo, afastado, etc.)." - - name: carga_horaria - description: > - Carga horária associada. - data_type: varchar # Mantido como varchar, pode ser convertido para integer/numeric no silver + - name: cod_uorg_exercicio + description: "Código da unidade organizacional onde o servidor exerce suas atividades." - - name: nome_cargo - description: > - Nome do cargo ocupado. - data_type: varchar + - name: cod_upag + description: "Código da Unidade Pagadora responsável pelo pagamento ao servidor." - - name: dt_mes_inicio - description: > - Data de início da experiência (formato YYYYMM), convertida para DATE (1º dia do mês). - data_type: date + - name: cod_orgao_origem + description: "Código do órgão de origem, em caso de movimentação funcional." - - name: nome_orgao_empresa - description: > - Nome do órgão ou empresa onde a experiência ocorreu. - data_type: varchar + - name: cpf_chefia_imediata + description: "CPF da chefia imediata, higienizado para conter apenas dígitos." - - name: dt_mes_fim - description: > - Data de fim da experiência (formato YYYYMM), convertida para DATE (1º dia do mês). - data_type: date + - name: dt_exercicio_no_orgao + description: "Data em que o servidor iniciou o exercício no órgão atual." - - name: descricao_projeto - description: > - Descrição detalhada do projeto ou atividades realizadas. - data_type: text # Usar 'text' para campos potencialmente longos ou multi-linha + - name: dt_fim_vale_ar + description: "Data final da vigência do vale de auxílio-refeição." - - name: informacoes_adicionais - description: > - Informações adicionais sobre a experiência. - data_type: text # Usar 'text' para campos potencialmente longos ou multi-linha + - name: dt_ingresso_funcao + description: "Data de ingresso na função atual." - - name: tipo_descricao - description: > - Tipo de descrição ou categoria adicional. - data_type: varchar + - name: dt_ocorr_ingresso_orgao + description: "Data da ocorrência de ingresso no órgão atual." - - name: dados_afastamento - description: > - Tabela bronze contendo dados de férias e afastamentos de servidores, - após limpeza básica e padronização de formatos a partir da fonte bruta. - Valores de data inválidos ou malformados na origem são convertidos para NULL. + - name: dt_ocorr_ingresso_serv_publico + description: "Data da ocorrência de ingresso no serviço público." - columns: - - name: adiantamento_salario_ferias - description: > - Indicador de adiantamento de salário nas férias (ex: N, S). - data_type: varchar + - name: email_chefia_imediata + description: "E-mail institucional da chefia imediata, padronizado em minúsculo." - - name: ano_exercicio - description: > - Ano de exercício das férias ou afastamento. - data_type: varchar # ou integer se sempre for numérico e sem zeros à esquerda significativos + - name: email_institucional + description: "E-mail institucional do servidor, padronizado em minúsculo." - - name: dt_fim - description: > - Data de fim das férias ou afastamento. - Convertida para DATE; strings de origem inválidas (ex: "0", comprimento incorreto) resultam em NULL. - data_type: date + - name: email_servidor + description: "E-mail pessoal do servidor, padronizado em minúsculo." - - name: dt_fim_aquisicao - description: > - Data de fim do período aquisitivo. - Convertida para DATE; strings de origem inválidas resultam em NULL. - data_type: date + - name: ident_unica + description: "Identificação única do servidor no sistema." - - name: dt_ini - description: > - Data de início das férias ou afastamento. - Convertida para DATE; strings de origem inválidas resultam em NULL. - data_type: date + - name: matricula_siape + description: "Matrícula do servidor no sistema SIAPE." - - name: dt_inicio_aquisicao - description: > - Data de início do período aquisitivo. - Convertida para DATE; strings de origem inválidas resultam em NULL. - data_type: date + - name: modalidade_pgd + description: "Modalidade de participação no Programa de Gestão e Desempenho (PGD)." - - name: gratificacao_natalina - description: > - Indicador de gratificação natalina (13º salário) (ex: N, S). - data_type: varchar + - name: nome_atividade_funcao + description: "Descrição textual da atividade ou função exercida." - - name: numero_parcela - description: > - Número da parcela das férias. - data_type: integer + - name: nome_chefe_uorg + description: "Nome da chefia imediata da unidade organizacional." - - name: parcela_continuacao_interrupcao - description: > - Indicador se a parcela é continuação ou interrupção. - data_type: varchar + - name: nome_funcao + description: "Nome da função ocupada." - - name: parcela_interrompida - description: > - Indicador se a parcela foi interrompida. - data_type: varchar + - name: nome_jornada + description: "Descrição da jornada de trabalho." - - name: qtde_dias - description: > - Quantidade de dias de férias/afastamento. - data_type: integer + - name: nome_ocorr_ingresso_orgao + description: "Descrição da ocorrência de ingresso no órgão." - - name: gr_matricula - description: > - Matrícula do servidor (possivelmente com código GR ou outro prefixo). - data_type: varchar + - name: nome_ocorr_ingresso_serv_publico + description: "Descrição da ocorrência de ingresso no serviço público." - - name: cpf - description: > - CPF do servidor, contendo apenas os 11 dígitos. - data_type: varchar(11) - # tests: - # - not_null - # - dbt_utils.equal_length: - # value: 11 + - name: nome_orgao + description: "Nome do órgão onde o servidor está vinculado." - - name: cod_diploma_afastamento - description: > - Código do diploma legal do afastamento. - data_type: varchar + - name: nome_regime_juridico + description: "Nome do regime jurídico do servidor ( Estatutário, CLT)." - - name: cod_ocorrencia - description: > - Código da ocorrência (férias/afastamento). - data_type: varchar + - name: nome_situacao_funcional + description: "Descrição da situação funcional do servidor." - - name: dt_publicacao_afastamento - description: > - Data de publicação do ato de afastamento. - Convertida para DATE (formato de origem YYYYMMDD); strings de origem inválidas resultam em NULL. - data_type: date + - name: nome_uorg_exercicio + description: "Nome da unidade organizacional de exercício." - - name: desc_diploma_afastamento - description: > - Descrição do diploma legal do afastamento (ex: PORTARIA). - data_type: varchar + - name: nome_upag + description: "Nome da unidade pagadora responsável pelo servidor." - - name: desc_ocorrencia - description: > - Descrição da ocorrência de afastamento. - data_type: varchar + - name: participa_pgd + description: "Indica se o servidor participa do Programa de Gestão e Desempenho." - - name: numero_diploma_afastamento - description: > - Número do diploma legal do afastamento. - data_type: integer + - name: percentual_ts + description: "Percentual de tempo de trabalho remoto (teletrabalho), convertido para valor numérico ( 0.75 representa 75%)." - - name: dt_inicio_ferias_interrompidas - description: > - Data de início das férias que foram interrompidas. - Convertida para DATE; strings de origem inválidas resultam em NULL. - data_type: date + - name: sigla_orgao + description: "Sigla do órgão atual." - - name: dias_restantes - description: > - Quantidade de dias restantes de férias após interrupção. - data_type: integer + - name: sigla_orgao_origem + description: "Sigla do órgão de origem." - - name: afastamento_historico - description: > - Tabela bronze contendo dados históricos de férias e afastamentos de servidores, (semelhante a dados_afastamento) - após limpeza básica e padronização de formatos a partir da fonte bruta. - Valores de data inválidos ou malformados na origem são convertidos para NULL. + - name: sigla_regime_juridico + description: "Sigla do regime jurídico do servidor." - columns: - - name: adiantamento_salario_ferias - description: > - Indicador de adiantamento de salário nas férias (ex: N, S). - data_type: varchar + - name: sigla_uorg_exercicio + description: "Sigla da unidade de exercício." - - name: gr_matricula - description: > - Matrícula do servidor (Não está presenta aqui, ficou como placeholder tudo null). - data_type: varchar + - name: sigla_upag + description: "Sigla da unidade pagadora." - - name: ano_exercicio - description: > - Ano de exercício das férias ou afastamento. - data_type: varchar + - name: cpf + description: "CPF do servidor, com apenas dígitos." - - name: dt_fim - description: > - Data de fim das férias ou afastamento. - Convertida para DATE; strings de origem inválidas resultam em NULL. - data_type: date + - name: cod_cargo + description: "Código do cargo efetivo ocupado pelo servidor." - - name: dt_fim_aquisicao - description: > - Data de fim do período aquisitivo. - Convertida para DATE; strings de origem inválidas resultam em NULL. - data_type: date + - name: cod_classe + description: "Código da classe funcional do cargo." - - name: dt_ini - description: > - Data de início das férias ou afastamento. - Convertida para DATE; strings de origem inválidas resultam em NULL. - data_type: date + - name: cod_ocorr_aposentadoria + description: "Código da ocorrência de aposentadoria." - - name: dt_inicio_aquisicao - description: > - Data de início do período aquisitivo. - Convertida para DATE; strings de origem inválidas resultam em NULL. - data_type: date + - name: dt_ini_vale_ar + description: "Data de início do benefício de auxílio-refeição." - - name: dt_inicio_ferias_interrompidas - description: > - Data de início das férias que foram interrompidas. - Convertida para DATE; strings de origem inválidas resultam em NULL. - data_type: date + - name: dt_ocorr_aposentadoria + description: "Data da ocorrência de aposentadoria." - - name: dias_restantes - description: > - Quantidade de dias restantes de férias após interrupção. - data_type: integer + - name: nome_cargo + description: "Nome do cargo efetivo." - - name: gratificacao_natalina - description: > - Indicador de gratificação natalina (13º salário) (ex: N, S). - data_type: varchar + - name: nome_classe + description: "Nome da classe funcional do cargo." - - name: numero_parcela - description: > - Número da parcela das férias. - data_type: integer + - name: nome_ocorr_aposentadoria + description: "Descrição da ocorrência de aposentadoria." + + - name: sigla_nivel_cargo + description: "Sigla do nível do cargo ocupado." + + - name: tipo_vale_ar + description: "Tipo de auxílio-refeição concedido." + + - name: cod_ocorr_isencao_ir + description: "Código da ocorrência de isenção de imposto de renda." + + - name: dt_ini_ocorr_isencao_ir + description: "Data de início da ocorrência de isenção de IR." + + - name: nome_ocorr_isencao_ir + description: "Descrição da ocorrência de isenção de imposto de renda." + + - name: cod_uorg_lotacao + description: "Código da unidade de lotação do servidor." + + - name: nome_uorg_lotacao + description: "Nome da unidade de lotação do servidor." - - name: parcela_continuacao_interrupcao - description: > - Indicador se a parcela é continuação ou interrupção. - data_type: varchar + - name: sigla_uorg_lotacao + description: "Sigla da unidade de lotação do servidor." - - name: parcela_interrompida - description: > - Indicador se a parcela foi interrompida. - data_type: varchar + - name: dt_fim_ocorr_isencao_ir + description: "Data de término da isenção de IR." - - name: qtde_dias - description: > - Quantidade de dias de férias/afastamento. - data_type: integer + - name: cod_ocorr_exclusao + description: "Código da ocorrência de exclusão do servidor do sistema." - - name: cpf - description: > - CPF do servidor, contendo apenas os 11 dígitos. - data_type: varchar(11) - # tests: - # - not_null - # - dbt_utils.equal_length: - # value: 11 + - name: dt_ocorr_exclusao + description: "Data da ocorrência de exclusão do servidor." - - name: cod_diploma_afastamento - description: > - Código do diploma legal do afastamento. - data_type: varchar + - name: nome_ocorr_exclusao + description: "Descrição da ocorrência de exclusão do servidor." - - name: cod_ocorrencia - description: > - Código da ocorrência (férias/afastamento). - data_type: varchar + - name: dt_uorg_lotacao + description: "Data da lotação na unidade organizacional atual." - - name: dt_publicacao_afastamento - description: > - Data de publicação do ato de afastamento. - Convertida para DATE (formato de origem YYYYMMDD); strings de origem inválidas resultam em NULL. - data_type: date + - name: cod_vale_transporte + description: "Código do benefício de vale transporte." - - name: desc_diploma_afastamento - description: > - Descrição do diploma legal do afastamento (ex: PORTARIA). - data_type: varchar + - name: valor_vale_transporte + description: "Valor monetário do vale transporte." - - name: desc_ocorrencia - description: > - Descrição da ocorrência de afastamento. - data_type: varchar + - name: dt_uorg_exercicio + description: "Data de início do exercício na unidade organizacional atual." - - name: numero_diploma_afastamento + - name: pontuacao_desempenho description: > - Número do diploma legal do afastamento. - data_type: integer - - # Pessoas DBT + Pontuação atribuída ao servidor conforme avaliação de desempenho. + Pode conter letras (A, B, C) ou valores numéricos, depende da origem. Mantido como string. - # TED DBT - - name: pf_tesouro - description: > - Esta tabela contém registros de Programações Financeiras extraídas do SIAFI, detalhando informações como tipo de programação, data de execução e valor. - A tabela é atualizada diariamente e reflete as autorizações de movimentação financeira no âmbito da execução orçamentária federal. - columns: - - name: emissao_mes - description: > - Mês de emissão da Programação Financeira. - - name: emissao_dia - description: > - Dia de emissão da Programação Financeira. - - name: ug_emitente - description: > - Código da Unidade Gestora responsável pela emissão da Programação Financeira. - - name: ug_emitente_descricao - description: > - Nome ou descrição da Unidade Gestora emitente da Programação Financeira. - - name: ug_favorecido - description: > - Código da Unidade Gestora favorecida pela Programação Financeira. - - name: ug_favorecido_descricao - description: > - Nome ou descrição da Unidade Gestora favorecida pela Programação Financeira. - - name: pf_evento - description: > - Código do evento contábil associado à Programação Financeira, representando a natureza da transação. - - name: pf_evento_descricao - description: > - Descrição do evento contábil vinculado à Programação Financeira. - - name: pf - description: > - Número identificador único da Programação Financeira. - - name: pf_inscricao - description: > - Número de inscrição da Programação Financeira, utilizado para controle e acompanhamento. - - name: pf_acao - description: > - Código da ação orçamentária associada à Programação Financeira. - - name: pf_acao_descricao - description: > - Descrição da ação orçamentária vinculada à Programação Financeira. - - name: pf_fonte_recursos - description: > - Código da fonte de recursos associada à Programação Financeira, indicando a origem dos recursos utilizados. - - name: pf_fonte_recursos_descricao - description: > - Descrição textual da fonte de recursos vinculada à Programação Financeira. - - name: pf_vinculacao_pagamento - description: > - Código da vinculação de pagamento associada à Programação Financeira. - - name: pf_vinculacao_pagamento_descricao - description: > - Descrição da vinculação de pagamento vinculada à Programação Financeira. - - name: pf_categoria_gasto - description: > - Código da categoria de gasto associada à Programação Financeira, conforme classificação orçamentária. - - name: pf_recurso - description: > - Código do recurso associado à Programação Financeira. - - name: pf_recurso_descricao - description: > - Descrição do recurso vinculado à Programação Financeira. - - name: doc_observacao - description: > - Observações adicionais relacionadas à Programação Financeira. - - name: pf_valor_linha - description: > - Valor monetário individual da linha da Programação Financeira. - data_tests: - - verificacao_tipagem: - nome_tabela: 'ted.pf_tesouro' - nome_coluna: 'emissao_dia' - tipo_esperado: 'date' - - verificacao_tipagem: - nome_tabela: 'ted.pf_tesouro' - nome_coluna: 'pf_valor_linha' - tipo_esperado: 'numeric' - - name: nc_tesouro + - name: dados_pa description: > - Esta tabela registra informações detalhadas sobre as Notas de Crédito emitidas no âmbito da execução orçamentária e financeira do governo federal. - As Notas de Crédito representam autorizações para a realização de despesas, sendo fundamentais para o controle e acompanhamento da execução orçamentária. + Tabela bronze com dados de pensionistas e alimentados. + meta: + tags: + - bronze columns: - - name: emissao_mes - description: > - Mês de emissão da Nota de Crédito. - - name: emissao_dia - description: > - Dia de emissão da Nota de Crédito. - - name: nc - description: > - Número identificador único da Nota de Crédito. - - name: nc_transferencia - description: > - Indicador de que a Nota de Crédito refere-se a uma transferência de recursos entre unidades gestoras. - - name: nc_fonte_recursos - description: > - Código da fonte de recursos associada à Nota de Crédito, indicando a origem dos recursos utilizados. - - name: nc_fonte_recursos_descricao - description: > - Descrição textual da fonte de recursos vinculada à Nota de Crédito. - - name: ptres - description: > - Código do Plano Interno de Trabalho (PTRES) relacionado à Nota de Crédito, utilizado para detalhar a alocação dos recursos. - - name: nc_evento - description: > - Código do evento contábil associado à Nota de Crédito, representando a natureza da transação. - - name: nc_evento_descricao - description: > - Descrição do evento contábil vinculado à Nota de Crédito. - - name: ug_responsavel - description: > - Código da Unidade Gestora responsável pela emissão da Nota de Crédito. - - name: ug_responsavel_descricao - description: > - Nome ou descrição da Unidade Gestora responsável pela emissão da Nota de Crédito. - - name: natureza_despesa - description: > - Código da natureza da despesa associada à Nota de Crédito, conforme classificação orçamentária. - - name: natureza_despesa_detalhada - description: > - Descrição detalhada da natureza da despesa vinculada à Nota de Crédito. - - name: plano_interno - description: > - Código do plano interno relacionado à Nota de Crédito, utilizado para controle interno da execução orçamentária. - - name: plano_detalhado_descricao1 - description: > - Primeira descrição detalhada do plano interno associado à Nota de Crédito. - - name: plano_detalhado_descricao2 - description: > - Segunda descrição detalhada do plano interno associado à Nota de Crédito. - - name: favorecido_doc - description: > - Documento de identificação (CPF ou CNPJ) do favorecido pela Nota de Crédito. - - name: favorecido_doc_descricao - description: > - Nome ou razão social do favorecido identificado na Nota de Crédito. - - name: nc_valor_linha - description: > - Valor monetário individual da linha da Nota de Crédito. - - name: movimento_liquido - description: > - Valor líquido do movimento financeiro associado à Nota de Crédito, após deduções ou acréscimos aplicáveis. + - name: agencia_beneficiario + description: "Agência bancária do beneficiário." + - name: banco_beneficiario + description: "Banco do beneficiário." + - name: cod_orgao + description: "Código do órgão." + - name: conta_beneficiario + description: "Conta bancária do beneficiário." + - name: cpf_beneficiario + description: "CPF do beneficiário." + - name: matricula_servidor + description: "Matrícula do servidor instituidor da pensão." + - name: nome_beneficiario + description: "Nome do beneficiário." + - name: valor_ultima_pensao + description: "Valor da última pensão." + - name: cpf_servidor + description: "CPF do servidor instituidor da pensão." + - name: cod_vinculo_servidor + description: "Código do vínculo com o servidor." + - name: nome_alimentado + description: "Nome do alimentado." + - name: nome_vinculo_servidor + description: "Nome do vínculo com o servidor." - - name: planos_acao + - name: dados_pessoais description: > - Esta tabela armazena informações detalhadas sobre os Planos de Ação vinculados a programas públicos. - Ela consolida dados sobre as unidades envolvidas na execução, prazos de vigência, valores, justificativas, - formas de execução e instrumentos utilizados. Serve como base para o acompanhamento, análise e auditoria - da implementação de ações públicas em diferentes formatos de execução. + Tabela bronze com dados pessoais dos servidores. + meta: + tags: + - bronze columns: - - name: id_plano_acao - description: > - Identificador único do Plano de Ação. - - name: id_programa - description: > - Identificador único do programa ao qual o Plano de Ação está vinculado. - - name: sigla_unidade_descentralizada - description: > - Sigla da unidade descentralizada responsável pelo Plano de Ação. - - name: unidade_descentralizada - description: > - Nome completo da unidade descentralizada responsável pelo Plano de Ação. - - name: sigla_unidade_responsavel_execucao - description: > - Sigla da unidade responsável pela execução do Plano de Ação. - - name: unidade_responsavel_execucao - description: > - Nome completo da unidade responsável pela execução do Plano de Ação. - - name: vl_total_plano_acao - description: > - Valor total previsto para execução do Plano de Ação. - - name: dt_inicio_vigencia - description: > - Data de início da vigência do Plano de Ação. - - name: dt_fim_vigencia - description: > - Data final da vigência do Plano de Ação. - - name: tx_objeto_plano_acao - description: > - Descrição do objeto do Plano de Ação, informando seu propósito e escopo. - - name: tx_justificativa_plano_acao - description: > - Justificativa para a elaboração e execução do Plano de Ação. - - name: in_forma_execucao_direta - description: > - Indicador booleano que sinaliza se a execução do plano ocorrerá de forma direta. - - name: in_forma_execucao_particulares - description: > - Indicador booleano que sinaliza se a execução contará com a participação de particulares. - - name: in_forma_execucao_descentralizada - description: > - Indicador booleano que sinaliza se a execução será realizada de forma descentralizada. - - name: tx_situacao_plano_acao - description: > - Situação atual do Plano de Ação (ex: em elaboração, aprovado, em execução, concluído). - - name: aa_ano_plano_acao - description: > - Ano de referência do Plano de Ação. - - name: vl_beneficiario_especifico - description: > - Valor destinado especificamente a beneficiários definidos no Plano de Ação. - - name: vl_chamamento_publico - description: > - Valor previsto para execução por meio de chamamento público. - - name: sq_instrumento - description: > - Código sequencial do instrumento jurídico ou administrativo associado ao Plano de Ação. - - name: aa_instrumento - description: > - Ano de referência do instrumento associado ao Plano de Ação. + - name: cod_cor + description: "Código da cor/raça." + - name: cod_estado_civil + description: "Código do estado civil." + - name: cod_nacionalidade + description: "Código da nacionalidade." + - name: cod_sexo + description: "Código do sexo." + - name: dt_nascimento + description: "Data de nascimento." + - name: grupo_sanguineo + description: "Grupo sanguíneo." + - name: nome_pessoa + description: "Nome da pessoa." + - name: nome_cor + description: "Nome da cor/raça." + - name: nome_estado_civil + description: "Nome do estado civil." + - name: nome_mae + description: "Nome da mãe." + - name: nome_municipio_nascimento + description: "Nome do município de nascimento." + - name: nome_nacionalidade + description: "Nome da nacionalidade." + - name: nome_pai + description: "Nome do pai." + - name: nome_sexo + description: "Nome do sexo." + - name: num_pispasep + description: "Número do PIS/PASEP." + - name: uf_nascimento + description: "UF de nascimento." + - name: cpf + description: "CPF do servidor." + - name: cod_deficiencia_fisica + description: "Código de deficiência física." + - name: nome_deficiencia_fisica + description: "Nome da deficiência física." + - name: dt_chegada_brasil + description: "Data de chegada ao Brasil." + - name: nome_pais_origem + description: "Nome do país de origem." - - name: nota_credito + - name: dados_uorg description: > - Esta tabela contém informações sobre as Notas de Crédito emitidas no contexto dos Planos de Ação. - As notas de crédito representam movimentações financeiras entre unidades gestoras e gestões, sendo - vinculadas a um Plano de Ação específico. A tabela registra dados como número, data de emissão, - códigos das unidades e gestões envolvidas, além de observações e situação da nota. + Tabela bronze com dados das Unidades Organizacionais (UORGs). + meta: + tags: + - bronze columns: - - name: id_nota - description: > - Identificador único da Nota de Crédito. - - name: id_plano_acao - description: > - Identificador único do Plano de Ação ao qual a Nota de Crédito está vinculada. - - name: tx_minuta_nota - description: > - Texto ou rascunho da minuta da Nota de Crédito. - - name: tx_numero_nota - description: > - Número da Nota de Crédito, utilizado para controle e rastreabilidade. - - name: dt_emissao_nota - description: > - Data e hora da emissão da Nota de Crédito. - - name: cd_gestao_emitente_nota - description: > - Código da gestão pública que emitiu a Nota de Crédito. - - name: cd_gestao_favorecida_nota - description: > - Código da gestão pública favorecida pela Nota de Crédito. - - name: tx_situacao_nota - description: > - Situação atual da Nota de Crédito (ex: emitida, cancelada, aprovada). - - name: cd_ug_emitente_nota - description: > - Código da Unidade Gestora que emitiu a Nota de Crédito. - - name: cd_ug_favorecida_nota - description: > - Código da Unidade Gestora favorecida pela Nota de Crédito. - - name: tx_observacao_nota - description: > - Observações adicionais registradas na Nota de Crédito, quando houver. + - name: bairro_uorg + description: "Bairro da UORG." + - name: cep_uorg + description: "CEP da UORG." + - name: codigo_matricula + description: "Código de matrícula." + - name: codigo_municipio_uorg + description: "Código do município da UORG." + - name: codigo_orgao + description: "Código do órgão." + - name: codigo_orgao_uorg + description: "Código da UORG no órgão." + - name: email_uorg + description: "Email da UORG." + - name: tipo_endereco_uorg + description: "Tipo de endereço da UORG." + - name: logradouro_uorg + description: "Logradouro da UORG." + - name: nome_municipio_uorg + description: "Nome do município da UORG." + - name: nome_uorg + description: "Nome da UORG." + - name: telefone_uorg + description: "Telefone da UORG." + - name: numero_endereco_uorg + description: "Número do endereço da UORG." + - name: sigla_uorg + description: "Sigla da UORG." + - name: uf_uorg + description: "UF da UORG." + - name: cpf + description: "CPF associado à UORG." + - name: complemento_endereco_uorg + description: "Complemento do endereço da UORG." + - name: fax_uorg + description: "Fax da UORG." - - name: programacao_financeira + + - name: estrutura_organizacional_cargos description: > - Esta tabela armazena os registros de Programações Financeiras vinculadas aos Planos de Ação. - A Programação Financeira representa o planejamento e execução da alocação de recursos entre unidades gestoras. - A tabela contempla informações como tipo, número, situação, unidade emitente e favorecida, - bem como observações e o documento hábil de recebimento. + Tabela gold contendo a estrutura organizacional de unidades com informações expandidas de cargos e instâncias. + Os dados foram transformados para extrair e normalizar os campos aninhados no JSON armazenado na coluna `cargos`. + A tabela inclui apenas uma instância por código, priorizando a de maior ordem de grandeza. + meta: + tags: + - bronze columns: - - name: id_programacao - description: > - Identificador único da Programação Financeira. - - name: id_plano_acao - description: > - Identificador único do Plano de Ação ao qual a Programação Financeira está vinculada. - - name: tp_pf_tipo_programacao - description: > - Tipo de Programação Financeira (ex: ordinária, suplementar, especial). - - name: tx_minuta_programacao - description: > - Minuta ou rascunho da Programação Financeira. - - name: tx_numero_programacao - description: > - Número da Programação Financeira, utilizado para controle interno e rastreamento. - - name: tx_situacao_programacao - description: > - Situação atual da Programação Financeira (ex: em análise, aprovada, cancelada). - - name: tx_observacao_programacao - description: > - Observações complementares sobre a Programação Financeira, se houver. - - name: ug_emitente_programacao - description: > - Código ou nome da Unidade Gestora que emitiu a Programação Financeira. - - name: ug_favorecida_programacao - description: > - Código ou nome da Unidade Gestora favorecida pela Programação Financeira. - - name: dh_recebimento_programacao - description: > - Data e hora do recebimento do documento hábil relacionado à Programação Financeira. + - name: codigounidade + description: Código da unidade organizacional. + - name: nomeunidade + description: Nome completo da unidade organizacional. + - name: siglaunidade + description: Sigla da unidade organizacional. + - name: municipio + description: Município onde a unidade está localizada. + - name: uf + description: Unidade federativa (UF) correspondente ao município. + - name: denominacao + description: Denominação do cargo. + - name: funcao + description: Função associada à denominação do cargo. + - name: codigo_instancia + description: Código da instância do cargo na estrutura. + - name: nome_titular + description: Nome do servidor titular do cargo. + - name: cpf_titular + description: CPF do servidor titular do cargo (com apenas dígitos numéricos). + - name: ordem_grandeza + description: Nível hierárquico da unidade na estrutura, utilizado para escolher a instância mais relevante. + + + - name: lista_servidores + description: > + Tabela bronze com a lista de servidores e suas UORGs. + meta: + tags: + - bronze + columns: + - name: cod_uorg + description: "Código da UORG." + - name: dt_ultima_transacao + description: "Data da última transação." + - name: cpf + description: "CPF do servidor." - - name: programas + - name: lista_uorgs description: > - Esta tabela contém os registros dos Programas institucionais, que orientam e organizam os Planos de Ação - sob responsabilidade de diferentes unidades da administração pública. A tabela agrega dados como código, - nome, ano, situação, descrição, objetivos e informações relacionadas às autorizações e tipos de investimentos, - além de indicar critérios específicos como beneficiários e chamamentos públicos. + Tabela bronze com a lista de UORGs. + meta: + tags: + - bronze columns: - - name: id_programa - description: > - Identificador único do Programa. - - name: tx_codigo_programa - description: > - Código atribuído ao Programa para fins de controle e identificação institucional. - - name: aa_ano_programa - description: > - Ano de referência do Programa. - - name: tx_situacao_programa - description: > - Situação atual do Programa (ex: ativo, encerrado, em elaboração). - - name: tx_nome_programa - description: > - Nome oficial do Programa. - - name: sigla_unidade_descentralizadora - description: > - Sigla da unidade responsável pela descentralização dos recursos do Programa. - - name: unidade_descentralizadora - description: > - Nome completo da unidade descentralizadora responsável pelo Programa. - - name: sigla_unidade_responsavel_acompanhamento - description: > - Sigla da unidade responsável pelo acompanhamento do Programa. - - name: unidade_responsavel_acompanhamento - description: > - Nome completo da unidade responsável pelo acompanhamento do Programa. - - name: tx_nome_institucional_programa - description: > - Nome institucional utilizado oficialmente para identificar o Programa. - - name: tx_objetivo_programa - description: > - Objetivo principal do Programa, descrevendo seu propósito estratégico. - - name: tx_descricao_programa - description: > - Descrição detalhada do Programa, abrangendo seu escopo e diretrizes. - - name: in_grupo_investimento_obra - description: > - Indicador booleano que sinaliza se o Programa está vinculado a investimento em obras. - - name: in_grupo_investimento_servico - description: > - Indicador booleano que sinaliza se o Programa está vinculado a investimento em serviços. - - name: in_grupo_investimento_equipamento - description: > - Indicador booleano que sinaliza se o Programa está vinculado a investimento em equipamentos. - - name: in_autoriza_subdescentralizacao_outro - description: > - Indicador que informa se o Programa autoriza subdescentralização para outras unidades. - - name: in_autoriza_realizacao_despesas - description: > - Indicador que informa se o Programa autoriza a realização de despesas diretamente. - - name: in_autoriza_execucao_creditos_descentralizada - description: > - Indicador que informa se o Programa autoriza a execução descentralizada de créditos. - - name: in_beneficiario_especifico - description: > - Indicador booleano de existência de beneficiário específico no Programa. - - name: dt_recebimento_plano_beneficiario_inicio - description: > - Data de início para recebimento do plano de beneficiário específico vinculado ao Programa. - - name: dt_recebimento_plano_beneficiario_fim - description: > - Data final para recebimento do plano de beneficiário específico vinculado ao Programa. - - name: in_chamamento_publico - description: > - Indicador booleano de execução via chamamento público no âmbito do Programa. - - name: dt_recebimento_plano_chamamento_inicio - description: > - Data de início para recebimento do plano relacionado a chamamento público. - - name: dt_recebimento_plano_chamamento_fim - description: > - Data final para recebimento do plano relacionado a chamamento público. + - name: codigo + description: "Código da UORG." + - name: dt_ultima_transacao + description: "Data da última transação." + - name: nome + description: "Nome da UORG." + + + - name: terceirizados + description: > + Tabela gold contendo informações de trabalhadores terceirizados oriundos do sistema ComprasGov. + Inclui dados relacionados ao contrato, função, jornada, remuneração e benefícios, com tratamento de campos textuais e numéricos para padronização. + meta: + tags: + - bronze + columns: + - name: id + description: Identificador único do registro do trabalhador terceirizado. + - name: contrato_id + description: Identificador do contrato ao qual o trabalhador está vinculado. + - name: cpf + description: CPF do trabalhador, extraído da string `usuario`. + - name: nome + description: Nome do trabalhador, extraído da string `usuario`. + - name: funcao_id + description: Identificador da função exercida pelo trabalhador. + - name: descricao_complementar + description: Descrição complementar da função ou atividade do trabalhador. + - name: jornada + description: Quantidade de horas de jornada de trabalho semanal do trabalhador (convertido para numérico). + - name: unidade + description: Unidade organizacional onde o trabalhador presta serviço. + - name: salario + description: Valor do salário mensal do trabalhador (convertido para formato numérico padrão). + - name: custo + description: Custo total do trabalhador para a instituição (convertido para formato numérico padrão). + - name: escolaridade_id + description: Identificador do grau de escolaridade do trabalhador. + - name: data_inicio + description: Data de início do contrato do trabalhador, convertida para formato de data. + - name: data_fim + description: Data de término do contrato do trabalhador, convertida para formato de data. + - name: situacao + description: Situação atual do vínculo contratual do trabalhador (ativo, encerrado). + - name: aux_transporte + description: Valor mensal do auxílio transporte recebido pelo trabalhador (convertido para numérico). + - name: vale_alimentacao + description: Valor mensal do vale alimentação recebido pelo trabalhador (convertido para numérico). + + + - name: unidade_organizacional + description: > + Tabela gold que representa a hierarquia das unidades organizacionais extraídas do sistema Siorg. + A hierarquia é construída de forma recursiva, partindo de uma unidade raiz definida manualmente. + A tabela traz dados estruturados sobre as unidades, seus vínculos parentais e o nível de profundidade hierárquica. + meta: + tags: + - bronze + columns: + - name: codigounidade + description: Código da unidade organizacional (sem prefixos de URI). + - name: codigounidadepai + description: Código da unidade organizacional pai, representando a hierarquia entre unidades. + - name: codigoorgaoentidade + description: Código do órgão ou entidade ao qual a unidade pertence. + - name: codigotipounidade + description: Código do tipo da unidade organizacional (departamento, secretaria). + - name: nome + description: Nome completo da unidade organizacional. + - name: sigla + description: Sigla da unidade organizacional. + - name: codigoesfera + description: Código da esfera governamental (federal, estadual, municipal). + - name: codigopoder + description: Código do poder ao qual a unidade está vinculada (Executivo, Judiciário). + - name: codigonaturezajuridica + description: Código da natureza jurídica da unidade. + - name: codigosubnaturezajuridica + description: Código da subnatureza jurídica da unidade. + - name: nivelnormatizacao + description: Nível de normatização da unidade organizacional. + - name: versaoconsulta + description: Número da versão da consulta no momento da extração dos dados. + - name: datafinalversaoconsulta + description: Data de finalização da versão da consulta. + - name: operacao + description: Tipo de operação registrada (inclusão, alteração, exclusão). + - name: codigounidadepaianterior + description: Código da unidade pai anterior (em caso de alteração estrutural). + - name: codigoorgaoentidadeanterior + description: Código do órgão/entidade anterior da unidade (em caso de mudança). + - name: ordem_grandeza + description: Nível hierárquico da unidade dentro da estrutura organizacional (quanto maior, mais profunda). + - name: caminho_unidade + description: Caminho hierárquico concatenado das siglas das unidades até o nível atual. + + + # Silver + - name: afastamento_consolidado + description: > + Tabela gold que consolida os registros de afastamento dos servidores, integrando as fontes + `dados_afastamento` e `afastamento_historico`, com enriquecimento de informações vindas de + `dados_pessoais` (nome do servidor) e `dados_funcionais` (função e unidade de exercício). + O processo inclui deduplicação inteligente priorizando os registros mais confiáveis, + utilizando lógica de ranking e partição por CPF e data de início do afastamento. + meta: + tags: + - silver + columns: + - name: adiantamento_salario_ferias + description: Indica se houve adiantamento de salário junto às férias no período do afastamento. + - name: ano_exercicio + description: Ano de exercício a que o afastamento se refere. + - name: dt_fim + description: Data de término do afastamento. + - name: dt_fim_aquisicao + description: Data final do período aquisitivo das férias relacionadas ao afastamento. + - name: dt_ini + description: Data de início do afastamento. + - name: dt_inicio_aquisicao + description: Data inicial do período aquisitivo de férias. + - name: dt_inicio_ferias_interrompidas + description: Data de início das férias interrompidas, se houver. + - name: dias_restantes + description: Quantidade de dias restantes de férias ou afastamento. + - name: gratificacao_natalina + description: Indica se houve gratificação natalina no período do afastamento. + - name: numero_parcela + description: Número da parcela relacionada ao afastamento. + - name: parcela_continuacao_interrupcao + description: Indica se o afastamento é continuação ou interrupção de uma parcela anterior. + - name: parcela_interrompida + description: Indica se a parcela foi interrompida. + - name: qtde_dias + description: Quantidade total de dias do afastamento. + - name: cpf + description: CPF do servidor. + - name: cod_diploma_afastamento + description: Código do diploma legal que ampara o afastamento. + - name: cod_ocorrencia + description: Código da ocorrência de afastamento. + - name: dt_publicacao_afastamento + description: Data de publicação do afastamento no diário oficial ou sistema correspondente. + - name: desc_diploma_afastamento + description: Descrição textual do diploma legal de afastamento. + - name: desc_ocorrencia + description: Descrição da ocorrência relacionada ao afastamento. + - name: numero_diploma_afastamento + description: Número do diploma legal do afastamento. + - name: gr_matricula + description: Número da matrícula do servidor no sistema GRH, quando disponível. + - name: origem_dados + description: Origem do dado de afastamento (`dados_afastamento` ou `afastamento_historico`). + - name: nome_pessoa + description: Nome completo do servidor, obtido a partir da tabela de dados pessoais. + - name: cod_funcao + description: Código da função de chefia ou cargo de confiança ocupado pelo servidor no momento do afastamento. + - name: sigla_uorg_exercicio + description: Sigla da unidade organizacional onde o servidor exercia suas funções. + + + + - name: quantitativo_alocados_ocupados + description: > + Tabela gold que consolida e compara os quantitativos de cargos ocupados e cargos previstos + em funções de chefia, correlacionando os dados dos sistemas SIAPE e SIORG. A lógica aplica + uma combinação heurística de códigos de função com base na estrutura dos códigos e siglas + das unidades organizacionais, com objetivo de identificar inconsistências, sobras ou + vacâncias entre as vagas disponíveis e as efetivamente ocupadas. + meta: + tags: + - silver + columns: + - name: codigo_siape + description: Código da função do servidor registrado no SIAPE (`cod_funcao`), representando funções ocupadas. + - name: codigo_siorg + description: Código da função de chefia conforme registrado no SIORG (`funcao`), representando vagas previstas. + - name: nomeunidade + description: Nome da unidade organizacional, obtido da base SIORG ou SIAPE (com prioridade para SIORG). + - name: siglaunidade + description: Sigla da unidade organizacional, unificada a partir das duas fontes (com prioridade para SIORG). + - name: nome_cargo + description: Nome ou denominação do cargo/função de chefia, com base em SIORG ou SIAPE. + - name: qtd_vagas_cargo + description: Quantidade de vagas previstas para o cargo de chefia na estrutura organizacional (dados do SIORG). + - name: qtd_vagas_ocupadas + description: Quantidade de cargos de chefia efetivamente ocupados, com base nos registros de servidores no SIAPE. + - name: qtd_cargos_vagos + description: > + Diferença entre o número de vagas previstas (`qtd_vagas_cargo`) e as ocupadas (`qtd_vagas_ocupadas`). + Indica o total de cargos vagos. Retorna `null` se a quantidade de vagas previstas não estiver disponível. + + + - name: servidores_detalhados + description: > + Tabela gold que consolida dados pessoais, funcionais, educacionais e organizacionais dos servidores. + Integra informações de múltiplas fontes do SIAPE, combinando registros de identificação, vínculo funcional, + escolaridade, endereço da unidade de exercício e metadados de transações. Essa visão é útil para análises + completas do perfil dos servidores ativos e inativos, permitindo estudos sociodemográficos, funcionais e + institucionais detalhados. + meta: + tags: + - silver + columns: + # Dados pessoais + - name: cpf + description: CPF do servidor, utilizado como chave primária de junção entre as bases. + - name: nome_pessoa + description: Nome completo do servidor. + - name: dt_nascimento + description: Data de nascimento do servidor. + - name: nome_cor + description: Cor/raça autodeclarada do servidor. + - name: nome_estado_civil + description: Estado civil declarado. + - name: nome_mae + description: Nome da mãe do servidor. + - name: nome_pai + description: Nome do pai do servidor. + - name: nome_municipio_nascimento + description: Município de nascimento do servidor. + - name: nome_nacionalidade + description: Nacionalidade declarada. + - name: nome_sexo + description: Sexo/gênero do servidor. + + # Dados funcionais + - name: matricula_siape + description: Matrícula SIAPE do servidor. + - name: nome_funcao + description: Nome da função de chefia ou cargo comissionado exercido, se houver. + - name: cod_funcao + description: Código da função exercida. + - name: nome_cargo + description: Nome do cargo efetivo. + - name: cod_cargo + description: Código do cargo efetivo. + - name: nome_jornada + description: Jornada de trabalho do servidor. + - name: dt_ingresso_funcao + description: Data de ingresso na função atual. + - name: nome_regime_juridico + description: Regime jurídico do vínculo funcional. + - name: nome_situacao_funcional + description: Situação funcional (ativo, cedido, aposentado etc). + - name: participa_pgd + description: Indica se o servidor participa do Programa de Gestão (teletrabalho). - - name: empenhos_plano_acao + # Escolaridade e titulação + - name: nome_escolaridade_principal + description: Nível de escolaridade mais elevado do servidor. + - name: nome_titulacao_principal + description: Título acadêmico mais elevado do servidor ( mestrado, doutorado). + + # Unidade organizacional + - name: nome_uorg_exercicio + description: Nome da unidade organizacional onde o servidor exerce atualmente. + - name: sigla_uorg_exercicio + description: Sigla da unidade de exercício. + - name: nome_uorg_lotacao + description: Nome da unidade de lotação original. + - name: sigla_uorg_lotacao + description: Sigla da unidade de lotação. + - name: nome_orgao_funcional + description: Nome do órgão funcional ao qual o servidor está vinculado. + - name: sigla_orgao_funcional + description: Sigla do órgão funcional. + + # Endereço da unidade + - name: logradouro_uorg + description: Nome do logradouro da unidade. + - name: numero_endereco_uorg + description: Número do endereço da unidade. + - name: complemento_endereco_uorg + description: Complemento do endereço. + - name: bairro_uorg + description: Bairro onde a unidade está localizada. + - name: cep_uorg + description: CEP da unidade. + - name: nome_municipio_uorg + description: Município da unidade. + - name: uf_uorg + description: Unidade federativa da unidade. + + # Contatos institucionais + - name: email_institucional + description: Email institucional do servidor. + - name: email_servidor + description: Email alternativo do servidor. + - name: email_chefia_imediata + description: Email da chefia imediata. + - name: telefone_uorg + description: Telefone da unidade de exercício. + - name: fax_uorg + description: Fax da unidade de exercício. + + # Metadados e auditoria + - name: ident_unica_funcional + description: Identificador único funcional, usado para rastreamento interno. + - name: dt_ultima_transacao_servidor + description: Data da última transação registrada no sistema para o servidor. + + # Informações complementares + - name: cod_ocorr_aposentadoria + description: Código da ocorrência de aposentadoria (se houver). + - name: dt_ocorr_aposentadoria + description: Data da aposentadoria. + - name: cod_vale_transporte + description: Código do vale transporte. + - name: valor_vale_transporte + description: Valor mensal do vale transporte recebido. + - name: pontuacao_desempenho + description: Pontuação de desempenho do servidor, se aplicável. + + + + + - name: tabela_correlacao_cargos description: > - Esta tabela estabelece a relação entre os empenhos registrados no SIAFI e os respectivos Planos de Ação, - permitindo o rastreamento das despesas públicas desde a origem do crédito até sua execução. + Tabela final que correlaciona cargos entre os sistemas SIAPE e SIORG, utilizando + combinações de códigos e unidades organizacionais. Cada linha representa um servidor + com informações enriquecidas da função e da chefia, além da hierarquia do cargo e + dados pessoais básicos. Essa tabela é útil para analisar discrepâncias, estruturar + a hierarquia funcional e verificar se as funções atribuídas no SIAPE estão + corretamente refletidas no SIORG. + + meta: + tags: + - silver columns: - - name: ne - description: > - Número da Nota de Empenho, extraído dos 12 últimos dígitos do campo `ne_ccor`, representando o identificador do empenho no SIAFI. - - name: num_transf - description: > - Número da transferência identificado na descrição da nota de empenho (`ne_ccor_descricao`), utilizado para vincular o empenho ao Plano de Ação correspondente. - - name: nc - description: > - Código da Nota de Crédito associado ao empenho, extraído da descrição da nota de empenho e formatado conforme padrão estabelecido. - - name: plano_acao - description: > - Identificador do Plano de Ação relacionado ao empenho, obtido a partir da correspondência com o número de transferência (`num_transf`). - - name: ne_ccor - description: > - Campo original contendo o código completo da Nota de Empenho, utilizado como base para extração de identificadores. - - name: ne_ccor_descricao - description: > - Descrição textual da Nota de Empenho, de onde são extraídos os números de transferência e códigos de nota de crédito para associação com os Planos de Ação. - - name: demais_colunas - description: > - Outras colunas provenientes da tabela `empenhos_tesouro`, contendo informações adicionais sobre os empenhos, como data de emissão, valor, unidade gestora, entre outras. + - name: codigo_siape + description: Código da função no SIAPE. + - name: codigo_siorg + description: Código da função no SIORG. + - name: codigo_combinacao_siape + description: Código combinado utilizado para comparar cargos no SIAPE. + - name: codigo_combinacao_siorg + description: Código combinado utilizado para comparar cargos no SIORG. + - name: matricula_siape + description: Matrícula do servidor no sistema SIAPE. + - name: cpf + description: CPF do servidor titular do cargo. + - name: cpf_chefia_imediata + description: CPF da chefia imediata do servidor. + - name: cod_situacao_funcional + description: Código da situação funcional do servidor no SIAPE. + - name: nome_situacao_funcional + description: Descrição da situação funcional. + - name: hierarquia_cargo + description: Valor numérico que representa a posição hierárquica do cargo. Quanto menor, mais alto o cargo. + - name: servidor + description: Nome do servidor titular do cargo. + - name: dt_nascimento + description: Data de nascimento do servidor. + - name: nome_sexo + description: Sexo do servidor. + - name: nome_estado_civil + description: Estado civil do servidor. + - name: nome_nacionalidade + description: Nacionalidade do servidor. + - name: nome_cor + description: Cor/raça autodeclarada pelo servidor. + - name: uf_nascimento + description: Unidade Federativa (UF) de nascimento. + - name: nome_municipio_nascimento + description: Município de nascimento do servidor. + - name: nome_chefia + description: Nome da chefia imediata, obtido pelo CPF da chefia. + - name: codigounidade + description: Código da unidade organizacional onde o cargo está lotado. + - name: codigounidadepai + description: Código da unidade organizacional imediatamente superior. + - name: caminho_unidade + description: Caminho hierárquico da unidade organizacional, representando sua posição na estrutura. + - name: ordem_grandeza + description: Nível de profundidade da unidade na hierarquia institucional. + - name: nomeunidade + description: Nome da unidade organizacional. + - name: siglaunidade + description: Sigla da unidade organizacional. + - name: nome_cargo + description: Denominação do cargo ocupado, conforme SIAPE ou SIORG. + - name: servidores_carreira + description: Classificação se o cargo é de carreira ou nomeação livre. - - name: nc_plano_acao + + - name: unidades_organizacionais_siorg_siape description: > - Esta tabela estabelece a relação entre as Notas de Crédito registradas no SIAFI e os respectivos Planos de Ação, - permitindo o rastreamento das movimentações financeiras desde a origem do crédito até sua aplicação. + Tabela que correlaciona as unidades organizacionais do SIAPE e do SIORG, + com base na correspondência entre os códigos e siglas das unidades. + Essa tabela é utilizada para identificar quais unidades estão presentes + em ambos os sistemas, apenas no SIAPE ou apenas no SIORG. + + meta: + tags: + - silver columns: - - name: plano_acao - description: > - Identificador do Plano de Ação relacionado à Nota de Crédito, obtido a partir da correspondência com o número de transferência (`nc_transferencia`). - - name: emissao_dia - description: > - Dia de emissão da Nota de Crédito. - - name: num_transf - description: > - Número da transferência associado à Nota de Crédito, utilizado para vincular a movimentação financeira ao Plano de Ação correspondente. - - name: nc - description: > - Código da Nota de Crédito, extraído dos 12 últimos dígitos do campo `nc`, representando o identificador da nota no SIAFI. - - name: nc_fonte_recursos - description: > - Código da fonte de recursos associada à Nota de Crédito, indicando a origem dos recursos utilizados. - - name: ptres - description: > - Código do Plano Interno de Trabalho (PTRES) vinculado à Nota de Crédito, representando a ação orçamentária correspondente. - - name: ug_emitente - description: > - Código da Unidade Gestora emitente da Nota de Crédito, extraído dos 6 primeiros dígitos do campo `nc`. - - name: nc_natureza_despesa - description: > - Código da natureza da despesa associada à Nota de Crédito, conforme classificação orçamentária. - - name: nc_evento - description: > - Código do evento contábil associado à Nota de Crédito, representando a natureza da transação. - - name: nc_evento_descr - description: > - Descrição do evento contábil vinculado à Nota de Crédito. - - name: nc_valor - description: > - Valor monetário da Nota de Crédito, ajustado conforme o tipo de evento contábil. + - name: nome_unidade + description: Nome da unidade organizacional, proveniente do SIAPE ou SIORG. + - name: sigla_uorg + description: Sigla da unidade organizacional. Pode vir do SIAPE (dados_uorg) ou do SIORG (unidade_organizacional). + - name: codigo_unidade_siape + description: Código da unidade organizacional segundo o SIAPE (extraído de dados_uorg). + - name: codigo_unidade_siorg + description: Código da unidade organizacional segundo o SIORG (coluna codigounidade). + - name: tipo_correlacao + description: > + Tipo de correspondência encontrada entre os sistemas: + - "ambos": unidade presente no SIAPE e SIORG. + - "apenas_siorg": presente apenas no SIORG. + - "apenas_siape": presente apenas no SIAPE. - - name: pf_plano_acao + + # Gold + - name: aposentadorias_resumo description: > - Esta tabela consolida as programações financeiras executadas no âmbito do SIAFI que estão relacionadas a Planos de Ação. + Tabela de resumo dos servidores aposentados, contendo informações detalhadas + sobre a data de ingresso no serviço público, data de aposentadoria e tempo + de serviço público calculado em anos, meses e dias. A granularidade da tabela + é por CPF, ou seja, cada linha representa um servidor aposentado único. + + meta: + tags: + - gold columns: - - name: pf - description: > - Número identificador da Programação Financeira, conforme registrado no SIAFI. - - name: num_transf - description: > - Número da transferência extraído do campo `pf_inscricao`, utilizado como chave alternativa para vinculação com Planos de Ação. - - name: emissao_mes - description: > - Mês da emissão da Programação Financeira, no formato numérico (1 a 12). - - name: emissao_dia - description: > - Dia da emissão da Programação Financeira, no formato numérico (1 a 31). - - name: ug_emitente - description: > - Código da Unidade Gestora responsável pela emissão da Programação Financeira. - - name: ug_favorecido - description: > - Código da Unidade Gestora favorecida pela Programação Financeira. - - name: pf_evento - description: > - Código do evento contábil associado à Programação Financeira, que descreve o tipo de movimentação registrada. - - name: pf_evento_descricao - description: > - Descrição do evento contábil da Programação Financeira, fornecendo contexto adicional sobre o tipo de operação realizada. - - name: pf_acao - description: > - Código da ação orçamentária extraído da descrição da programação financeira, representando a finalidade da despesa. - - name: pf_valor_linha - description: > - Valor programado para execução financeira, referente à linha específica da Programação Financeira. - - name: plano_acao - description: > - Identificador do Plano de Ação associado à Programação Financeira, determinado por correspondência direta com o sistema TransfereGov ou via número de transferência. + - name: cpf + description: Número do CPF do servidor aposentado. + - name: nome_pessoa + description: Nome completo do servidor aposentado. + - name: dt_ocorr_ingresso_serv_publico + description: Data de ingresso do servidor no serviço público. + - name: dt_ocorr_aposentadoria + description: Data de aposentadoria do servidor. + - name: mes_aposentadoria + description: Mês da aposentadoria (arredondado para o primeiro dia do mês). + - name: nome_situacao_funcional + description: Situação funcional do servidor no momento da aposentadoria. + - name: nome_ocorr_aposentadoria + description: Tipo de ocorrência que levou à aposentadoria do servidor. + - name: nome_cargo + description: Cargo ocupado pelo servidor no momento da aposentadoria. + - name: sigla_nivel_cargo + description: Sigla do nível do cargo ocupado. + - name: classe_padrao + description: Classe e padrão do cargo, no formato "classe-padrão". + - name: age + description: Diferença completa entre as datas de ingresso e aposentadoria, em formato de intervalo. + - name: diff_anos + description: Quantidade de anos entre o ingresso no serviço público e a aposentadoria. + - name: diff_meses + description: Quantidade de meses entre o ingresso e a aposentadoria (ignora anos completos). + - name: diff_dias + description: Quantidade de dias entre o ingresso e a aposentadoria (ignora anos e meses completos). - - name: ted_resumo_orcamentario + + + - name: cargos_consolidado description: > - Tabela de consolidação orçamentária e financeira por Plano de Ação, baseada em informações de valores firmados, orçamentos recebidos e devolvidos, empenhos, despesas e transferências financeiras. - Os dados são integrados de diferentes fontes (notas de crédito, empenhos e programações financeiras) para fornecer um resumo abrangente da execução orçamentária e financeira de transferências intergovernamentais. + Tabela que consolida informações de cargos ocupados e vagos no serviço público, + a partir da junção entre os dados do SIAPE (servidores ativos e detalhados) e + os dados do SIORG (estrutura de cargos disponíveis). Essa junção é feita com + base no CPF do titular do cargo. Quando os campos do SIAPE estão nulos, o cargo + provavelmente está vago. A tabela pode ser utilizada para identificar cargos + vagos por unidade organizacional. + + meta: + tags: + - gold columns: - - name: plano_acao - description: > - Identificador único do Plano de Ação associado aos registros orçamentários e financeiros. - - name: num_transf - description: > - Número da transferência utilizado como chave de ligação entre diferentes fontes de dados. - - name: sigla_unidade_descentralizada - description: > - Sigla da Unidade Descentralizada responsável pelo Plano de Ação. - - name: ted_beneficiario_emitente - description: > - Identifica se a Unidade do Plano de Ação é a beneficiária ou a emitente dos recursos da TED (Transferência Voluntária). Valores possíveis: 'beneficiario', 'emitente' ou 'nao_indicado'. - - name: valor_firmado - description: > - Valor total originalmente acordado no Plano de Ação como meta de transferência de recursos. - - name: orcamento_recebido - description: > - Total de créditos orçamentários efetivamente recebidos por meio de Notas de Crédito. - - name: orcamento_devolvido - description: > - Total de créditos orçamentários devolvidos, identificado por eventos contábeis específicos (ex.: 300301, 300307). - - name: empenhado - description: > - Soma dos valores empenhados, ou seja, das despesas formalizadas no orçamento, excluindo anulações. - - name: empenho_anulado - description: > - Total de valores de empenhos anulados, ou seja, revertidos após sua emissão. - - name: despesas_pagas_exercicio - description: > - Total de despesas pagas no exercício corrente. - - name: despesas_pagas_rap - description: > - Total de despesas pagas com recursos de Restos a Pagar. - - name: restos_a_pagar - description: > - Valor total inscrito como Restos a Pagar, ou seja, despesas empenhadas e não pagas até o fim do exercício. - - name: despesas_liquidada - description: > - Soma das despesas liquidadas, indicando bens ou serviços efetivamente entregues. - - name: financeiro_recebido - description: > - Total de recursos financeiros recebidos via Programação Financeira (TED), considerando apenas ações classificadas como 'TRANSFERENCIA'. - - name: financeiro_devolvido - description: > - Total de recursos financeiros devolvidos, com ações classificadas como 'DEVOLUCAO'. - - name: financeiro_cancelado - description: > - Valor total de cancelamentos financeiros identificados na programação, com ação 'CANCELAMENTO'. + - name: siorg_cod_unidade + description: Código da unidade organizacional no SIORG. + - name: siorg_nome_unidade + description: Nome da unidade organizacional no SIORG. + - name: siorg_sigla_unidade + description: Sigla da unidade organizacional no SIORG. + - name: siorg_municipio_unidade + description: Município da unidade organizacional no SIORG. + - name: siorg_uf_unidade + description: Unidade federativa (UF) da unidade organizacional no SIORG. + - name: siorg_denominacao_cargo + description: Denominação do cargo conforme registrado no SIORG. + - name: siorg_funcao + description: Função comissionada associada ao cargo no SIORG. + - name: siorg_cod_instancia_cargo + description: Código da instância do cargo no SIORG. + - name: siorg_cpf_titular + description: CPF do titular do cargo segundo o SIORG. + - name: siorg_nome_titular + description: Nome do titular do cargo segundo o SIORG. + + - name: siape_cpf + description: CPF do servidor conforme os dados do SIAPE. + - name: siape_nome_pessoa + description: Nome completo do servidor no SIAPE. + - name: siape_nome_cargo_efetivo + description: Nome do cargo efetivo ocupado pelo servidor no SIAPE. + - name: siape_nome_funcao_comissionada + description: Nome da função comissionada ocupada pelo servidor no SIAPE. + - name: siape_cod_uorg + description: Código da unidade de exercício do servidor no SIAPE. + - name: siape_nome_uorg + description: Nome da unidade de exercício do servidor no SIAPE. + - name: siape_sigla_uorg + description: Sigla da unidade de exercício do servidor no SIAPE. + - name: siape_uf_uorg + description: Unidade federativa (UF) da unidade de exercício no SIAPE. + - name: siape_situacao_funcional + description: Situação funcional atual do servidor segundo o SIAPE. + + + + - name: hierarquia + description: > + Tabela que correlaciona dados do SIAPE (dados funcionais e pessoais dos servidores) + com a estrutura organizacional do SIORG, atribuindo uma métrica de hierarquia aos cargos + com base na codificação da função. A hierarquia é determinada por uma fórmula baseada + na categoria e no nível do cargo. A tabela também indica se o cargo é de carreira ou de + nomeação livre, além de consolidar informações sobre o servidor e sua chefia imediata. + + meta: + tags: + - gold + columns: + - name: codigo_siape + description: Código da função no SIAPE. + - name: codigo_siorg + description: Código da função no SIORG, com espaços removidos. + - name: codigo_combinacao_siape + description: Código combinado derivado da função no SIAPE, usado para correlação. + - name: codigo_combinacao_siorg + description: Código combinado derivado da função no SIORG, usado para correlação. + - name: matricula_siape + description: Matrícula funcional do servidor no SIAPE. + - name: cpf + description: CPF do servidor. + - name: cpf_chefia_imediata + description: CPF da chefia imediata do servidor. + - name: cod_situacao_funcional + description: Código da situação funcional do servidor. + - name: nome_situacao_funcional + description: Descrição da situação funcional do servidor. + - name: hierarquia_cargo + description: Indicador numérico da hierarquia do cargo (quanto menor, maior o cargo). + - name: servidor + description: Nome do servidor ocupante do cargo. + - name: dt_nascimento + description: Data de nascimento do servidor. + - name: nome_sexo + description: Sexo do servidor. + - name: nome_estado_civil + description: Estado civil do servidor. + - name: nome_nacionalidade + description: Nacionalidade do servidor. + - name: nome_cor + description: Cor/raça do servidor. + - name: uf_nascimento + description: Unidade federativa de nascimento do servidor. + - name: nome_municipio_nascimento + description: Município de nascimento do servidor. + - name: nome_chefia + description: Nome da chefia imediata do servidor. + - name: codigounidade + description: Código da unidade organizacional associada ao cargo. + - name: codigounidadepai + description: Código da unidade organizacional pai (superior). + - name: caminho_unidade + description: Caminho hierárquico completo da unidade. + - name: ordem_grandeza + description: Nível hierárquico da unidade na estrutura organizacional. + - name: nomeunidade + description: Nome da unidade organizacional. + - name: siglaunidade + description: Sigla da unidade organizacional. + - name: nome_cargo + description: Nome ou denominação do cargo ocupado. + - name: servidores_carreira + description: Indica se o servidor está em cargo de carreira ou em nomeação livre. - - name: num_transf_n_plano_acao + + - name: resumo_quadro_pessoal description: > - View responsável por mapear o número de transferência (`num_transf`) ao respectivo Plano de Ação (`plano_acao`). - Utiliza dados de Notas de Crédito do TransfereGov e do SIAFI para realizar o cruzamento entre diferentes sistemas. + Tabela resumo com a distribuição dos servidores públicos por cargo efetivo, gênero, + situação funcional e unidade federativa da unidade de exercício (UF). A granularidade é agregada, + e cada linha representa uma combinação única desses atributos, com a respectiva contagem de servidores. + + meta: + tags: + - gold columns: - - name: num_transf - description: > - Número da transferência financeira obtido a partir dos registros do SIAFI (nota de crédito). Serve como chave para integrar informações entre diferentes fontes, como notas de crédito, programações financeiras e empenhos. - - name: plano_acao - description: > - Identificador único do Plano de Ação, conforme registrado no TransfereGov. É atribuído ao `num_transf` com base no cruzamento entre a nota de crédito (NC) e a unidade gestora emitente (UG). + - name: cargo_efetivo + description: > + Nome do cargo efetivo ocupado pelo servidor. Caso não informado, é preenchido com 'N/A'. + - name: genero + description: > + Gênero do servidor (masculino, feminino ou outro), conforme informado no cadastro pessoal. + Em casos de ausência de informação, é preenchido com 'N/A'. + - name: situacao_funcional + description: > + Situação funcional atual do servidor ( Ativo, Aposentado, Cedido, etc). + Valores ausentes são substituídos por 'N/A'. + - name: localidade_uf + description: > + Unidade Federativa (UF) da unidade organizacional onde o servidor exerce suas funções. + Valores ausentes são preenchidos com 'N/A'. + - name: quantidade_servidores + description: > + Quantidade total de servidores distintos (com base no CPF) que se enquadram na combinação dos atributos anteriores. + macros: - name: create_udfs @@ -3092,4 +2953,14 @@ macros: type: text description: > Texto a ser convertido em data. - O texto deve estar no formato MÊS(texto)/ANO(numero). Ex.: FEV/2024 -> 2024-02-01 \ No newline at end of file + O texto deve estar no formato MÊS(texto)/ANO(numero). Ex.: FEV/2024 -> 2024-02-01 + + + + + + + + + + From c5e7c6d25b8935dbd8704324303fabbaa4f29be2 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Thu, 28 Aug 2025 18:08:01 -0300 Subject: [PATCH 147/317] fix(dag): muda estrategia de update da tabela visao orcamentaria --- .../visao_orcamentaria_ingest.py | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/visao_orcamentaria_ingest.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/visao_orcamentaria_ingest.py index 57184ab4..79bb18b4 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/visao_orcamentaria_ingest.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/visao_orcamentaria_ingest.py @@ -273,14 +273,23 @@ def insert_and_clean_data(**context: Dict[str, Any]) -> None: # Garantir que todos os valores sejam strings para evitar problemas de tipo for col in df.columns: df[col] = df[col].astype(str) - # Limpar valores NaN/None que podem ter sido introduzidos pelo pandas df[col] = df[col].replace(["nan", "NaN", "None"], "") data = df.to_dict(orient="records") + if not data: + logging.warning( + "DataFrame está vazio após o processamento. Nada será inserido." + ) + return + postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) + # Dropa a tabela antes de inserir novos dados + db.drop_table_if_exists("visao_orcamentaria_total", schema="siafi") + logging.info("Tabela siafi.visao_orcamentaria_total dropada com sucesso.") + # Insere os dados db.insert_data( data, @@ -293,19 +302,8 @@ def insert_and_clean_data(**context: Dict[str, Any]) -> None: f"{len(data)} registros" ) - # Remove duplicados - column_mapping_with_year = { - **COLUMN_MAPPING, - 30: "ano_exercicio", - } - db.remove_duplicates( - "visao_orcamentaria_total", column_mapping_with_year, schema="siafi" - ) - - logging.info("Limpeza de duplicados concluída com sucesso.") - except Exception as e: - logging.error(f"Erro ao inserir dados ou limpar duplicados: {str(e)}") + logging.error(f"Erro ao inserir dados: {str(e)}") raise # Definição das tarefas From f5a3214f2a43e96cf1b82cb56422984146456a8f Mon Sep 17 00:00:00 2001 From: Carla Rocha Date: Mon, 8 Sep 2025 12:12:07 -0300 Subject: [PATCH 148/317] Update README.md --- README.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/README.md b/README.md index c9f949e8..1e9ecfd1 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,36 @@ +# Gov Hub BR - Transformando Dados em Valor para Gestão Pública + +O Gov Hub BR é uma iniciativa para enfrentar os desafios da fragmentação, redundância e inconsistências nos sistemas estruturantes do governo federal. O projeto busca transformar dados públicos em ativos estratégicos, promovendo eficiência administrativa, transparência e melhor tomada de decisão. A partir da integração de dados, gestores públicos terão acesso a informações qualificadas para subsidiar decisões mais assertivas, reduzir custos operacionais e otimizar processos internos. + +Potencializamos informações de sistemas como TransfereGov, Siape, Siafi, ComprasGov e Siorg para gerar diagnósticos estratégicos, indicadores confiáveis e decisões baseadas em evidências. + +![Informações do Projeto](docs/land/dist/images/imagem_informacoes.jpg) + +- Transparência pública e cultura de dados abertos +- Indicadores confiáveis para acompanhamento e monitoramento +- Decisões baseadas em evidências e diagnósticos estratégicos +- Exploração de inteligência artificial para gerar insights +- Gestão orientada a dados em todos os níveis + +## Fluxo/Arquitetura de Dados + +A arquitetura do Gov Hub BR é baseada na Arquitetura Medallion, em um fluxo de dados que permite a coleta, transformação e visualização de dados. + +![Fluxo de Dados](fluxo_dados.jpg) + +Para mais informações sobre o projeto, veja o nosso [e-book](docs/land/dist/ebook/GovHub_Livro-digital_0905.pdf). +E temos também alguns slides falando do projeto e como ele pode ajudar a transformar a gestão pública. + +[Slides](https://www.figma.com/slides/PlubQE0gaiBBwFAV5GcVlH/Gov-Hub---F%C3%B3rum-IA---Giga-candanga?node-id=5-131&t=hlLiJiwfyPEPRFys-1) + +## Apoio + +Esse trabalho é mantido pelo [Lab Livre](https://www.instagram.com/lab.livre/) e apoiado pelo [IPEA/Dides](https://www.ipea.gov.br/portal/categorias/72-estrutura-organizacional/210-dides-estrutura-organizacional). + +## Contato + +Para dúvidas, sugestões ou para contribuir com o projeto, entre em contato conosco: [lablivreunb@gmail.com](mailto:lablivreunb@gmail.com) + # Data Pipeline Project This project implements a modern data stack using Airflow, dbt, Jupyter, and Superset for data orchestration, transformation, analysis, and visualization. From b814560f2f9771cb5525e55bd96a6fb810e2be63 Mon Sep 17 00:00:00 2001 From: Carla Rocha Date: Mon, 8 Sep 2025 14:01:42 -0300 Subject: [PATCH 149/317] Update README.md --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1e9ecfd1..97c3b701 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ O Gov Hub BR é uma iniciativa para enfrentar os desafios da fragmentação, red Potencializamos informações de sistemas como TransfereGov, Siape, Siafi, ComprasGov e Siorg para gerar diagnósticos estratégicos, indicadores confiáveis e decisões baseadas em evidências. -![Informações do Projeto](docs/land/dist/images/imagem_informacoes.jpg) +![Informações do Projeto](https://github.com/GovHub-br/gov-hub/blob/main/docs/land/dist/images/imagem_informacoes.jpg) - Transparência pública e cultura de dados abertos - Indicadores confiáveis para acompanhamento e monitoramento @@ -16,9 +16,9 @@ Potencializamos informações de sistemas como TransfereGov, Siape, Siafi, Compr A arquitetura do Gov Hub BR é baseada na Arquitetura Medallion, em um fluxo de dados que permite a coleta, transformação e visualização de dados. -![Fluxo de Dados](fluxo_dados.jpg) +![Fluxo de Dados](https://github.com/GovHub-br/gov-hub/blob/main/fluxo_dados.jpg) -Para mais informações sobre o projeto, veja o nosso [e-book](docs/land/dist/ebook/GovHub_Livro-digital_0905.pdf). +Para mais informações sobre o projeto, veja o nosso [e-book](https://github.com/GovHub-br/gov-hub/blob/main/docs/land/dist/ebook/GovHub_Livro-digital_0905.pdf). E temos também alguns slides falando do projeto e como ele pode ajudar a transformar a gestão pública. [Slides](https://www.figma.com/slides/PlubQE0gaiBBwFAV5GcVlH/Gov-Hub---F%C3%B3rum-IA---Giga-candanga?node-id=5-131&t=hlLiJiwfyPEPRFys-1) @@ -31,6 +31,7 @@ Esse trabalho é mantido pelo [Lab Livre](https://www.instagram.com/lab.livre/) Para dúvidas, sugestões ou para contribuir com o projeto, entre em contato conosco: [lablivreunb@gmail.com](mailto:lablivreunb@gmail.com) + # Data Pipeline Project This project implements a modern data stack using Airflow, dbt, Jupyter, and Superset for data orchestration, transformation, analysis, and visualization. From 787b7d066f77a10231347b0ec8f36039fb2900c8 Mon Sep 17 00:00:00 2001 From: Marcus Martins Date: Fri, 19 Sep 2025 14:26:09 -0300 Subject: [PATCH 150/317] =?UTF-8?q?migra=C3=A7=C3=A3o=20de=20pipeline=20pa?= =?UTF-8?q?ra=20github?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflow/main.yaml | 228 +++++++++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 .github/workflow/main.yaml diff --git a/.github/workflow/main.yaml b/.github/workflow/main.yaml new file mode 100644 index 00000000..a43c74b7 --- /dev/null +++ b/.github/workflow/main.yaml @@ -0,0 +1,228 @@ +name: Python CI/CD + +on: + pull_request: + branches: + - main + types: [opened, synchronize, reopened] + push: + branches: + - main + paths: + - 'Dockerfile' + - 'requirements.txt' + - 'pyproject.toml' + workflow_dispatch: + +env: + PYTHON_VERSION: "3.11" + POETRY_VERSION: "1.8.5" + POETRY_VIRTUALENVS_IN_PROJECT: "true" + POETRY_CACHE_DIR: "/home/runner/.cache/poetry" + DBT_PROJECT_DIR: "${{ github.workspace }}/airflow_lappis/dags/dbt/ipea" + IMAGE_TAG: "ghcr.io/${{ github.repository }}/airflow-ipea:${{ github.sha }}" + +jobs: + analise_de_codigo: + name: Analisar Código + runs-on: ubuntu-latest + steps: + - name: Baixar o Repositório + uses: actions/checkout@v4 + + - name: Configuração do Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Cache de Dependências do Poetry + uses: actions/cache@v4 + with: + path: ${{ env.POETRY_CACHE_DIR }} + key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }} + restore-keys: | + ${{ runner.os }}-poetry- + + - name: Instalar Dependências do Projeto + run: | + pip install poetry==${{ env.POETRY_VERSION }} + poetry config virtualenvs.in-project true + poetry install --no-root --with dev --no-interaction + + - name: Executar Linter (Black, Flake8, MyPy, etc.) + run: poetry run make lint + + --- + + testes_unitarios: + name: Executar Testes Unitários + runs-on: ubuntu-latest + steps: + - name: Baixar o Repositório + uses: actions/checkout@v4 + + - name: Configurar Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Cache de Dependências do Poetry + uses: actions/cache@v4 + with: + path: ${{ env.POETRY_CACHE_DIR }} + key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }} + restore-keys: | + ${{ runner.os }}-poetry- + + - name: Instalar Dependências do Projeto + run: | + pip install poetry==${{ env.POETRY_VERSION }} + poetry config virtualenvs.in-project true + poetry install --no-root --with dev --no-interaction + + - name: Executar Pytest + run: poetry run pytest tests --junitxml=report.xml --cov-report=xml:coverage.xml + + - name: Enviar Relatório de Testes + uses: actions/upload-artifact@v4 + with: + name: relatorio-junit + path: report.xml + + - name: Enviar Relatório de Cobertura + uses: actions/upload-artifact@v4 + with: + name: relatorio-cobertura + path: coverage.xml + + --- + + analise_sonarcloud: + name: Análise com SonarCloud + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - name: Baixar o Repositório + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Configurar Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Instalar Dependências do Projeto + run: | + pip install poetry==${{ env.POETRY_VERSION }} + poetry install --no-root --with dev --no-interaction + + - name: Gerar Relatório de Cobertura + run: poetry run pytest tests --cov-report=xml:coverage.xml + + - name: Executar Análise SonarCloud + uses: SonarSource/sonarcloud-github-action@v2.2.0 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_ORGANIZATION: ${{ secrets.SONAR_ORG }} + with: + projectKey: ${{ secrets.SONAR_PROJECT_KEY }} + args: > + -Dsonar.exclusions=**/.cache/poetry/**/*,**/.venv/**/*,**/.cache/pip/**/* + -Dsonar.python.coverage.reportPaths=coverage.xml + + --- + + construir_e_publicar_docker: + name: Construir & Publicar Imagem Docker + runs-on: ubuntu-latest + needs: [analise_de_codigo, testes_unitarios, analise_sonarcloud] + if: github.ref == 'refs/heads/main' || github.event_name == 'pull_request' + steps: + - name: Baixar o Repositório + uses: actions/checkout@v4 + + - name: Configurar Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Acessar o GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Construir e Publicar Imagem Docker + uses: docker/build-push-action@v5 + with: + push: true + tags: ${{ env.IMAGE_TAG }} + context: . + + --- + + deploy_docs_dbt: + name: Deploy da Documentação do DBT + runs-on: ubuntu-latest + needs: [construir_e_publicar_docker] + if: github.ref == 'refs/heads/main' + steps: + - name: Baixar o Repositório + uses: actions/checkout@v4 + + - name: Instalar Dependências do DBT + run: | + sudo apt-get update + sudo apt-get -y install openvpn iputils-ping + pip install dbt-core dbt-postgres + + - name: Conectar à VPN Privada + env: + VPN_P12: ${{ secrets.VPN_P12 }} + VPN_USER: ${{ secrets.VPN_USER }} + VPN_PWD: ${{ secrets.VPN_PWD }} + CLIENT_OVPN: ${{ secrets.CLIENT_OVPN }} + run: | + mkdir -p /etc/openvpn + echo "$CLIENT_OVPN" > /etc/openvpn/client.ovpn + echo "$VPN_P12" | base64 --decode > /etc/openvpn/openvpn_ipea_vpn.p12 + chmod 600 /etc/openvpn/openvpn_ipea_vpn.p12 + echo "$VPN_USER" >/etc/openvpn/cred.txt + echo "$VPN_PWD" >>/etc/openvpn/cred.txt + chmod 600 /etc/openvpn/cred.txt + sudo openvpn --config /etc/openvpn/client.ovpn --auth-user-pass /etc/openvpn/cred.txt --verb 3 --daemon --log /tmp/ovpn.log + timeout=60 + until grep -q 'Initialization Sequence Completed' /tmp/ovpn.log; do + sleep 1 + (( timeout-- )) + if (( timeout == 0 )); then + echo "❌ VPN não inicializou em 60s" >&2 + cat /tmp/ovpn.log + exit 1 + fi + done + ping -c 4 10.0.0.73 || true + + - name: Gerar Documentação do DBT + run: | + cd ${{ env.DBT_PROJECT_DIR }} + dbt deps + dbt docs generate + mkdir -p "${{ github.workspace }}/public" + mv target/* "${{ github.workspace }}/public/" + + - name: Fazer o Upload dos Artefatos para o GitHub Pages + uses: actions/upload-pages-artifact@v3 + with: + path: 'public' + + - name: Finalizar Processo da VPN + if: always() + run: sudo pkill openvpn || true + + - name: Concluir Deploy para o GitHub Pages + uses: actions/deploy-pages@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + From fc50d94a42467561e74ef801eb8e056b59af4a93 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Tue, 30 Sep 2025 09:03:34 -0300 Subject: [PATCH 151/317] feat(docker): adiciona driver do postgres no superset --- Dockerfile.superset | 12 ++++++++++++ docker-compose.yml | 10 +++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 Dockerfile.superset diff --git a/Dockerfile.superset b/Dockerfile.superset new file mode 100644 index 00000000..3a301ac0 --- /dev/null +++ b/Dockerfile.superset @@ -0,0 +1,12 @@ +FROM apache/superset:latest + +USER root + +# Install PostgreSQL driver directly in the virtual environment site-packages +# This ensures the driver is available when Superset tries to connect to PostgreSQL +RUN /usr/local/bin/python -m pip install psycopg2-binary --target /app/.venv/lib/python3.10/site-packages/ + +# Install other useful database drivers for future use +RUN /usr/local/bin/python -m pip install pymysql --target /app/.venv/lib/python3.10/site-packages/ + +USER superset \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index d8a7ec4c..5492fb5b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -86,7 +86,9 @@ services: # Analytics Tools superset: - image: apache/superset:latest + build: + context: . + dockerfile: Dockerfile.superset # Custom Dockerfile with PostgreSQL drivers environment: SUPERSET_SECRET_KEY: 'supersetadmin' SUPERSET_ENV: development @@ -95,6 +97,12 @@ services: - "8088:8088" depends_on: - postgres + restart: always + healthcheck: + test: ["CMD", "curl", "--fail", "http://localhost:8088/health"] + interval: 30s + timeout: 10s + retries: 3 command: > /bin/sh -c " superset fab create-admin --username admin --firstname Admin --lastname User --email admin@superset.com --password admin && From bb1ce3a45719689c02f7817d77868656bda246f2 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Tue, 30 Sep 2025 10:45:50 -0300 Subject: [PATCH 152/317] fix/makefile --- .gitignore | 1 + Makefile | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 15898217..cdba53cd 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ dist/ **/*.lock __pycache__/ .poetry/ +requirements.generated.txt # Cache directories .cache/ diff --git a/Makefile b/Makefile index 96541abd..f48d1c98 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ setup: poetry config warnings.export false poetry lock poetry install --no-root --with dev - poetry export --without-hashes --format=requirements.txt > requirements.txt + poetry export --without-hashes --format=requirements.txt > requirements.generated.txt bash setup-git-hooks.sh format: From 17aa8cca7e3dfee20c1b5d065d31489d2a35ee53 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Tue, 14 Oct 2025 23:43:28 -0300 Subject: [PATCH 153/317] bug: retira servidores fora do orgao --- .../dags/dbt/ipea/models/pessoas_dbt/gold/hierarquia.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/hierarquia.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/hierarquia.sql index 1ff2e005..bfaf74ee 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/hierarquia.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/hierarquia.sql @@ -61,7 +61,7 @@ with left join {{ ref("unidade_organizacional") }} as uo on df.sigla_uorg_exercicio = uo.sigla - where dt_ocorr_aposentadoria is null + where dt_ocorr_aposentadoria is null and dt_ocorr_exclusao is null ), -- select count(*) from codigos_siape; From fc048dd1c6d4ec47016c950f3f098f38ef7b8826 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Mon, 20 Oct 2025 15:18:37 -0300 Subject: [PATCH 154/317] =?UTF-8?q?fix/dag-nota-de-cr=C3=A9dito?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data_ingest/tesouro_gerencial/nc_tesouro_ingest.dag.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/nc_tesouro_ingest.dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/nc_tesouro_ingest.dag.py index 18353a62..b9973421 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/nc_tesouro_ingest.dag.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/nc_tesouro_ingest.dag.py @@ -48,12 +48,12 @@ "enviadas": { "subject": "notas_credito_enviadas_devolvidas_a_partir_de_2024", "column_mapping": COLUMN_MAPPING, - "skiprows": 7, + "skiprows": 10, }, "recebidas": { "subject": "notas_credito_recebidas_a_partir_de_2024", "column_mapping": None, - "skiprows": 3, + "skiprows": 6, }, } From a727f39c067c255ac4ff815b005b81c99984ffcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Egewarth?= Date: Sat, 25 Oct 2025 14:00:37 -0300 Subject: [PATCH 155/317] =?UTF-8?q?feat:=20altera=20a=20ingest=C3=A3o=20de?= =?UTF-8?q?=20ncs=20e=20pfs=20de=20teds=20para=20usar=20o=20id=20do=20plan?= =?UTF-8?q?o=20de=20a=C3=A7=C3=A3o=20ao=20inv=C3=A9s=20da=20UG=20emitente?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notas_de_credito_ingest_dag.py | 21 +---- .../programacao_financeira_ingest_dag.py | 21 +---- airflow_lappis/plugins/cliente_postgres.py | 10 ++ airflow_lappis/plugins/cliente_ted.py | 92 +++++++------------ 4 files changed, 50 insertions(+), 94 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/notas_de_credito_ingest_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/notas_de_credito_ingest_dag.py index e47e68d8..4af1e31a 100644 --- a/airflow_lappis/dags/data_ingest/transfere_gov/notas_de_credito_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transfere_gov/notas_de_credito_ingest_dag.py @@ -24,26 +24,13 @@ def notas_de_credito_dag() -> None: def fetch_and_store_notas_de_credito() -> None: logging.info("Iniciando fetch_and_store_notas_de_credito") - orgao_alvo = Variable.get("airflow_orgao", default_var=None) - if not orgao_alvo: - logging.error("Variável airflow_orgao não definida!") - raise ValueError("airflow_orgao não definida") - - orgaos_config_str = Variable.get("airflow_variables", default_var="{}") - orgaos_config = yaml.safe_load(orgaos_config_str) - - ug_codes = orgaos_config.get(orgao_alvo, {}).get("codigos_ug", []) - - if not ug_codes: - logging.warning(f"Nenhum código UG encontrado para o órgão '{orgao_alvo}'") - return - api = ClienteTed() postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) + id_planos_acao = db.get_id_planos_acao() - for ug_code in ug_codes: - notas_de_credito = api.get_notas_de_credito_by_ug(ug_code) + for id_plano_acao in id_planos_acao: + notas_de_credito = api.get_notas_de_credito_by_id_plano_acao(id_plano_acao) if notas_de_credito: # Adicionar dt_ingest a cada nota for nota in notas_de_credito: @@ -57,7 +44,7 @@ def fetch_and_store_notas_de_credito() -> None: schema="transfere_gov", ) else: - logging.warning(f"Nenhuma nota de crédito encontrada para UG {ug_code}") + logging.warning(f"Nenhuma nota de crédito encontrada plano de ação {id_plano_acao}") fetch_and_store_notas_de_credito() diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/programacao_financeira_ingest_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/programacao_financeira_ingest_dag.py index 22887bfe..b0f40415 100644 --- a/airflow_lappis/dags/data_ingest/transfere_gov/programacao_financeira_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transfere_gov/programacao_financeira_ingest_dag.py @@ -24,26 +24,13 @@ def programacao_financeira_dag() -> None: def fetch_and_store_programacao_financeira() -> None: logging.info("Iniciando fetch_and_store_programacao_financeira") - orgao_alvo = Variable.get("airflow_orgao", default_var=None) - if not orgao_alvo: - logging.error("Variável airflow_orgao não definida!") - raise ValueError("airflow_orgao não definida") - - orgaos_config_str = Variable.get("airflow_variables", default_var="{}") - orgaos_config = yaml.safe_load(orgaos_config_str) - - ug_codes = orgaos_config.get(orgao_alvo, {}).get("codigos_ug", []) - - if not ug_codes: - logging.warning(f"Nenhum código UG encontrado para o órgão '{orgao_alvo}'") - return - api = ClienteTed() postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) + id_planos_acao = db.get_id_planos_acao() - for ug_code in ug_codes: - programacao_financeira = api.get_programacao_financeira_by_ug(ug_code) + for id_plano_acao in id_planos_acao: + programacao_financeira = api.get_programacao_financeira_by_id_plano_acao(id_plano_acao) if programacao_financeira: # Adicionar dt_ingest a cada item for item in programacao_financeira: @@ -58,7 +45,7 @@ def fetch_and_store_programacao_financeira() -> None: ) else: logging.warning( - f"Nenhuma programação financeira encontrada para UG {ug_code}" + f"Nenhuma programação financeira encontrada plano de ação {id_plano_acao}" ) fetch_and_store_programacao_financeira() diff --git a/airflow_lappis/plugins/cliente_postgres.py b/airflow_lappis/plugins/cliente_postgres.py index 37e5cc9f..b07401af 100755 --- a/airflow_lappis/plugins/cliente_postgres.py +++ b/airflow_lappis/plugins/cliente_postgres.py @@ -199,6 +199,16 @@ def get_id_programas(self) -> List[int]: cursor.execute(query) id_programas = [row[0] for row in cursor.fetchall()] return id_programas + + def get_id_planos_acao(self) -> List[int]: + """Extrai todos os IDs de planos de ação da tabela de planos de ação.""" + query = "SELECT id_plano_acao FROM transfere_gov.planos_acao" + + with psycopg2.connect(self.conn_str) as conn: + with conn.cursor() as cursor: + cursor.execute(query) + id_planos_acao = [row[0] for row in cursor.fetchall()] + return id_planos_acao def drop_table_if_exists(self, table_name: str, schema: str = "raw") -> None: """Remove a tabela se ela existir.""" diff --git a/airflow_lappis/plugins/cliente_ted.py b/airflow_lappis/plugins/cliente_ted.py index 49190515..0247c9af 100644 --- a/airflow_lappis/plugins/cliente_ted.py +++ b/airflow_lappis/plugins/cliente_ted.py @@ -52,66 +52,6 @@ def get_programa_by_id_programa(self, id_programa: str) -> list | None: ) return None - def get_notas_de_credito_by_ug(self, ug_code: int) -> list | None: - endpoint_1 = f"nota_credito?cd_ug_favorecida_nota=eq.{ug_code}" - endpoint_2 = f"nota_credito?cd_ug_emitente_nota=eq.{ug_code}" - - logging.info(f"Buscando notas de crédito para UG: {ug_code}") - - status_1, data_1 = self.request( - http.HTTPMethod.GET, endpoint_1, headers=self.BASE_HEADER - ) - status_2, data_2 = self.request( - http.HTTPMethod.GET, endpoint_2, headers=self.BASE_HEADER - ) - - if status_1 == http.HTTPStatus.OK and isinstance(data_1, list): - logging.info(f"Notas de crédito (favorecida) obtidas para UG {ug_code}") - else: - logging.warning(f"Falha ao buscar notas de crédito - Status: {status_1}") - data_1 = [] - - if status_2 == http.HTTPStatus.OK and isinstance(data_2, list): - logging.info(f"Notas de crédito (emitente) obtidas para UG {ug_code}") - else: - logging.warning(f"Falha ao buscar notas de crédito - Status: {status_2}") - data_2 = [] - - data = data_1 + data_2 - return data if data else None - - def get_programacao_financeira_by_ug(self, ug_code: int) -> list | None: - endpoint_1 = f"programacao_financeira?ug_favorecida_programacao=eq.{ug_code}" - endpoint_2 = f"programacao_financeira?ug_emitente_programacao=eq.{ug_code}" - - logging.info(f"Buscando programação financeira para UG: {ug_code}") - - status_1, data_1 = self.request( - http.HTTPMethod.GET, endpoint_1, headers=self.BASE_HEADER - ) - status_2, data_2 = self.request( - http.HTTPMethod.GET, endpoint_2, headers=self.BASE_HEADER - ) - - if status_1 == http.HTTPStatus.OK and isinstance(data_1, list): - logging.info(f"Programação financeira (favorecida) obtida para UG {ug_code}") - else: - logging.warning( - f"Falha ao buscar programação financeira - Status: {status_1}" - ) - data_1 = [] - - if status_2 == http.HTTPStatus.OK and isinstance(data_2, list): - logging.info(f"Programação financeira (emitente) obtida para UG {ug_code}") - else: - logging.warning( - f"Falha ao buscar programação financeira - Status: {status_2}" - ) - data_2 = [] - - data = data_1 + data_2 - return data if data else None - def get_planos_acao_by_id_programa(self, id_programa: str) -> list | None: endpoint = f"plano_acao?id_programa=eq.{id_programa}" @@ -152,3 +92,35 @@ def get_programas_by_sigla_unidade_descentralizadora(self, sigla: str) -> list | f"{sigla} with status: {status}" ) return None + + def get_notas_de_credito_by_id_plano_acao(self, id_plano_acao: int) -> list | None: + endpoint = f"nota_credito?id_plano_acao=eq.{id_plano_acao}" + + logging.info(f"Buscando notas de crédito pelo plano de ação: {id_plano_acao}") + + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER + ) + + if status == http.HTTPStatus.OK and isinstance(data, list): + logging.info(f"Notas de crédito obtidas para plano de ação {id_plano_acao}") + return data + else: + logging.warning(f"Falha ao buscar notas de crédito - Status: {status}") + return None + + def get_programacao_financeira_by_id_plano_acao(self, id_plano_acao: int) -> list | None: + endpoint = f"programacao_financeira?id_plano_acao=eq.{id_plano_acao}" + + logging.info(f"Buscando programação financeira pelo plano de ação: {id_plano_acao}") + + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER + ) + + if status == http.HTTPStatus.OK and isinstance(data, list): + logging.info(f"Programação financeira obtidas para plano de ação {id_plano_acao}") + return data + else: + logging.warning(f"Falha ao buscar programação financeira - Status: {status}") + return None \ No newline at end of file From 7f40e766c69d8a58dbc1be22fa45fca7d2e8426b Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Mon, 27 Oct 2025 12:08:31 -0300 Subject: [PATCH 156/317] fix/pf-tesouro-ingest --- .../pf_tesouro_ingest_dag.py | 72 +++++++++---------- 1 file changed, 32 insertions(+), 40 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/pf_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/pf_tesouro_ingest_dag.py index 95c4e99e..3c1751ed 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/pf_tesouro_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/pf_tesouro_ingest_dag.py @@ -1,4 +1,4 @@ -from typing import Dict, Any, Optional, List, cast +from typing import Dict, Any from airflow import DAG from airflow.operators.python import PythonOperator from airflow.models import Variable @@ -47,7 +47,7 @@ # Assuntos dos emails a serem processados EMAIL_SUBJECT_ENVIADAS = "programacoes_financeiras_enviadas_devolvidas_a_partir_de_2024" EMAIL_SUBJECT_RECEBIDAS = "programacoes_financeiras_recebidas_a_partir_de_2024" -SKIPROWS = 3 +SKIPROWS = 6 # Configurações da DAG with DAG( @@ -60,7 +60,7 @@ tags=["email", "pfs", "tesouro"], ) as dag: - def process_email_data_enviadas(**context: Dict[str, Any]) -> Optional[List[Dict]]: + def process_email_data_enviadas(**context: Dict[str, Any]) -> str: """ Função para processar os emails com programações financeiras enviadas. """ @@ -75,28 +75,22 @@ def process_email_data_enviadas(**context: Dict[str, Any]) -> Optional[List[Dict logging.info( "Iniciando o processamento dos emails de programações enviadas/devolvidas" ) - csv_data = cast( - Optional[List[Dict[Any, Any]]], - fetch_and_process_email( - IMAP_SERVER, - EMAIL, - PASSWORD, - SENDER_EMAIL, - EMAIL_SUBJECT_ENVIADAS, - COLUMN_MAPPING, - skiprows=SKIPROWS, - ), + csv_data = fetch_and_process_email( + IMAP_SERVER, + EMAIL, + PASSWORD, + SENDER_EMAIL, + EMAIL_SUBJECT_ENVIADAS, + COLUMN_MAPPING, + skiprows=SKIPROWS, ) if not csv_data: logging.warning( "Nenhum e-mail encontrado com o assunto de programações enviadas" ) - return [] + return "" - logging.info( - "CSV de PFs enviadas processado com sucesso. Dados encontrados: %s", - len(csv_data), - ) + logging.info("CSV de PFs enviadas processado com sucesso.") return csv_data except Exception as e: logging.error( @@ -105,7 +99,7 @@ def process_email_data_enviadas(**context: Dict[str, Any]) -> Optional[List[Dict ) raise - def process_email_data_recebidas(**context: Dict[str, Any]) -> Optional[List[Dict]]: + def process_email_data_recebidas(**context: Dict[str, Any]) -> str: """ Função para processar os emails com programações financeiras recebidas. """ @@ -120,28 +114,22 @@ def process_email_data_recebidas(**context: Dict[str, Any]) -> Optional[List[Dic logging.info( "Iniciando o processamento dos emails de programações recebidas..." ) - csv_data = cast( - Optional[List[Dict[Any, Any]]], - fetch_and_process_email( - IMAP_SERVER, - EMAIL, - PASSWORD, - SENDER_EMAIL, - EMAIL_SUBJECT_RECEBIDAS, - column_mapping=None, - skiprows=SKIPROWS, - ), + csv_data = fetch_and_process_email( + IMAP_SERVER, + EMAIL, + PASSWORD, + SENDER_EMAIL, + EMAIL_SUBJECT_RECEBIDAS, + column_mapping=None, + skiprows=SKIPROWS, ) if not csv_data: logging.warning( "Nenhum e-mail encontrado com o assunto de programações recebidas." ) - return [] + return "" - logging.info( - "CSV de PFs recebidas processado com sucesso. Dados encontrados: %s", - len(csv_data), - ) + logging.info("CSV de PFs recebidas processado com sucesso.") return csv_data except Exception as e: logging.error( @@ -149,22 +137,26 @@ def process_email_data_recebidas(**context: Dict[str, Any]) -> Optional[List[Dic ) raise - def combine_data(**context: Dict[str, Any]) -> List[Dict]: + def combine_data(**context: Dict[str, Any]) -> str: """ Função para combinar os dados dos dois emails. """ try: task_instance: Any = context["ti"] enviadas_data = ( - task_instance.xcom_pull(task_ids="process_emails_enviadas") or [] + task_instance.xcom_pull(task_ids="process_emails_enviadas") or "" ) recebidas_data = ( - task_instance.xcom_pull(task_ids="process_emails_recebidas") or [] + task_instance.xcom_pull(task_ids="process_emails_recebidas") or "" ) combined_data = enviadas_data + recebidas_data - logging.info(f"Dados combinados: {len(combined_data)} registros no total.") + if combined_data: + logging.info("Dados combinados com sucesso.") + else: + logging.warning("Nenhum dado encontrado em ambos os emails.") + return combined_data except Exception as e: logging.error(f"Erro ao combinar os dados: {str(e)}") From eb3d2cb11d28d090d65d776cb5072c38fddfe9e8 Mon Sep 17 00:00:00 2001 From: mat054 Date: Mon, 27 Oct 2025 15:19:07 -0300 Subject: [PATCH 157/317] feat(silver ted): adicionando schema da silver de dados de ted --- .../dbt/ipea/models/ted_dbt/silver/schema.yml | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/schema.yml diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/schema.yml b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/schema.yml new file mode 100644 index 00000000..451ea4c8 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/schema.yml @@ -0,0 +1,116 @@ +version: 2 + +models: + + - name: empenhos_plano_acao + description: > + Esta tabela estabelece a relação entre os empenhos registrados no SIAFI e os respectivos Planos de Ação, + permitindo o rastreamento das despesas públicas desde a origem do crédito até sua execução. + meta: + tags: + - silver + columns: + - name: ne + description: > + Número da Nota de Empenho, extraído dos 12 últimos dígitos do campo `ne_ccor`, representando o identificador do empenho no SIAFI. + - name: num_transf + description: > + Número da transferência identificado na descrição da nota de empenho (`ne_ccor_descricao`), utilizado para vincular o empenho ao Plano de Ação correspondente. + - name: nc + description: > + Código da Nota de Crédito associado ao empenho, extraído da descrição da nota de empenho e formatado conforme padrão estabelecido. + - name: plano_acao + description: > + Identificador do Plano de Ação relacionado ao empenho, obtido a partir da correspondência com o número de transferência (`num_transf`). + - name: ne_ccor + description: > + Campo original contendo o código completo da Nota de Empenho, utilizado como base para extração de identificadores. + - name: ne_ccor_descricao + description: > + Descrição textual da Nota de Empenho, de onde são extraídos os números de transferência e códigos de nota de crédito para associação com os Planos de Ação. + - name: demais_colunas + description: > + Outras colunas provenientes da tabela `empenhos_tesouro`, contendo informações adicionais sobre os empenhos, como data de emissão, valor, unidade gestora, entre outras. + + - name: nc_plano_acao + description: > + Esta tabela estabelece a relação entre as Notas de Crédito registradas no SIAFI e os respectivos Planos de Ação, + permitindo o rastreamento das movimentações financeiras desde a origem do crédito até sua aplicação. + meta: + tags: + - silver + columns: + - name: plano_acao + description: > + Identificador do Plano de Ação relacionado à Nota de Crédito, obtido a partir da correspondência com o número de transferência (`nc_transferencia`). + - name: emissao_dia + description: > + Dia de emissão da Nota de Crédito. + - name: num_transf + description: > + Número da transferência associado à Nota de Crédito, utilizado para vincular a movimentação financeira ao Plano de Ação correspondente. + - name: nc + description: > + Código da Nota de Crédito, extraído dos 12 últimos dígitos do campo `nc`, representando o identificador da nota no SIAFI. + - name: nc_fonte_recursos + description: > + Código da fonte de recursos associada à Nota de Crédito, indicando a origem dos recursos utilizados. + - name: ptres + description: > + Código do Plano Interno de Trabalho (PTRES) vinculado à Nota de Crédito, representando a ação orçamentária correspondente. + - name: ug_emitente + description: > + Código da Unidade Gestora emitente da Nota de Crédito, extraído dos 6 primeiros dígitos do campo `nc`. + - name: nc_natureza_despesa + description: > + Código da natureza da despesa associada à Nota de Crédito, conforme classificação orçamentária. + - name: nc_evento + description: > + Código do evento contábil associado à Nota de Crédito, representando a natureza da transação. + - name: nc_evento_descr + description: > + Descrição do evento contábil vinculado à Nota de Crédito. + - name: nc_valor + description: > + Valor monetário da Nota de Crédito, ajustado conforme o tipo de evento contábil. + + - name: pf_plano_acao + description: > + Esta tabela consolida as programações financeiras executadas no âmbito do SIAFI que estão relacionadas a Planos de Ação. + meta: + tags: + - silver + columns: + - name: pf + description: > + Número identificador da Programação Financeira, conforme registrado no SIAFI. + - name: num_transf + description: > + Número da transferência extraído do campo `pf_inscricao`, utilizado como chave alternativa para vinculação com Planos de Ação. + - name: emissao_mes + description: > + Mês da emissão da Programação Financeira, no formato numérico (1 a 12). + - name: emissao_dia + description: > + Dia da emissão da Programação Financeira, no formato numérico (1 a 31). + - name: ug_emitente + description: > + Código da Unidade Gestora responsável pela emissão da Programação Financeira. + - name: ug_favorecido + description: > + Código da Unidade Gestora favorecida pela Programação Financeira. + - name: pf_evento + description: > + Código do evento contábil associado à Programação Financeira, que descreve o tipo de movimentação registrada. + - name: pf_evento_descricao + description: > + Descrição do evento contábil da Programação Financeira, fornecendo contexto adicional sobre o tipo de operação realizada. + - name: pf_acao + description: > + Código da ação orçamentária extraído da descrição da programação financeira, representando a finalidade da despesa. + - name: pf_valor_linha + description: > + Valor programado para execução financeira, referente à linha específica da Programação Financeira. + - name: plano_acao + description: > + Identificador do Plano de Ação associado à Programação Financeira, determinado por correspondência direta com o sistema TransfereGov ou via número de transferência. From 1fb24a480561d3eb70acd074ffd4501d8c618b07 Mon Sep 17 00:00:00 2001 From: mat054 Date: Mon, 27 Oct 2025 15:19:38 -0300 Subject: [PATCH 158/317] feat(ted views): adicionando schema de views de ted --- .../dbt/ipea/models/ted_dbt/views/schema.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/ted_dbt/views/schema.yml diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/views/schema.yml b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/views/schema.yml new file mode 100644 index 00000000..3ad69e0b --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/views/schema.yml @@ -0,0 +1,16 @@ +version: 2 + +models: + + - name: num_transf_n_plano_acao + description: > + View responsável por mapear o número de transferência (`num_transf`) ao respectivo Plano de Ação (`plano_acao`). + Utiliza dados de Notas de Crédito do TransfereGov e do SIAFI para realizar o cruzamento entre diferentes sistemas. + columns: + - name: num_transf + description: > + Número da transferência financeira obtido a partir dos registros do SIAFI (nota de crédito). Serve como chave para integrar informações entre diferentes fontes, como notas de crédito, programações financeiras e empenhos. + - name: plano_acao + description: > + Identificador único do Plano de Ação, conforme registrado no TransfereGov. É atribuído ao `num_transf` com base no cruzamento entre a nota de crédito (NC) e a unidade gestora emitente (UG). + From ac8cb8f9fc250db197274dce0d7fd5f4da2be1ed Mon Sep 17 00:00:00 2001 From: mat054 Date: Mon, 27 Oct 2025 15:20:08 -0300 Subject: [PATCH 159/317] feat(ted gold): adicionando schema da pasta gold de teds --- .../dbt/ipea/models/ted_dbt/gold/schema.yml | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/schema.yml diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/schema.yml b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/schema.yml new file mode 100644 index 00000000..e054b408 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/schema.yml @@ -0,0 +1,61 @@ +version: 2 + +models: + + - name: ted_resumo_orcamentario + description: > + Tabela de consolidação orçamentária e financeira por Plano de Ação, baseada em informações de valores firmados, orçamentos recebidos e devolvidos, empenhos, despesas e transferências financeiras. + Os dados são integrados de diferentes fontes (notas de crédito, empenhos e programações financeiras) para fornecer um resumo abrangente da execução orçamentária e financeira de transferências intergovernamentais. + meta: + tags: + - gold + columns: + - name: plano_acao + description: > + Identificador único do Plano de Ação associado aos registros orçamentários e financeiros. + - name: num_transf + description: > + Número da transferência utilizado como chave de ligação entre diferentes fontes de dados. + - name: sigla_unidade_descentralizada + description: > + Sigla da Unidade Descentralizada responsável pelo Plano de Ação. + - name: ted_beneficiario_emitente + description: > + Identifica se a Unidade do Plano de Ação é a beneficiária ou a emitente dos recursos da TED (Transferência Voluntária). Valores possíveis: 'beneficiario', 'emitente' ou 'nao_indicado'. + - name: valor_firmado + description: > + Valor total originalmente acordado no Plano de Ação como meta de transferência de recursos. + - name: orcamento_recebido + description: > + Total de créditos orçamentários efetivamente recebidos por meio de Notas de Crédito. + - name: orcamento_devolvido + description: > + Total de créditos orçamentários devolvidos, identificado por eventos contábeis específicos (ex.: 300301, 300307). + - name: empenhado + description: > + Soma dos valores empenhados, ou seja, das despesas formalizadas no orçamento, excluindo anulações. + - name: empenho_anulado + description: > + Total de valores de empenhos anulados, ou seja, revertidos após sua emissão. + - name: despesas_pagas_exercicio + description: > + Total de despesas pagas no exercício corrente. + - name: despesas_pagas_rap + description: > + Total de despesas pagas com recursos de Restos a Pagar. + - name: restos_a_pagar + description: > + Valor total inscrito como Restos a Pagar, ou seja, despesas empenhadas e não pagas até o fim do exercício. + - name: despesas_liquidada + description: > + Soma das despesas liquidadas, indicando bens ou serviços efetivamente entregues. + - name: financeiro_recebido + description: > + Total de recursos financeiros recebidos via Programação Financeira (TED), considerando apenas ações classificadas como 'TRANSFERENCIA'. + - name: financeiro_devolvido + description: > + Total de recursos financeiros devolvidos, com ações classificadas como 'DEVOLUCAO'. + - name: financeiro_cancelado + description: > + Valor total de cancelamentos financeiros identificados na programação, com ação 'CANCELAMENTO'. + From 0d4360798c6684f0dd709c5d39317df1dc91f76c Mon Sep 17 00:00:00 2001 From: mat054 Date: Mon, 27 Oct 2025 15:20:44 -0300 Subject: [PATCH 160/317] feat(ted bronze): adicionando schema da pasta bronze de teds --- .../dbt/ipea/models/ted_dbt/bronze/schema.yml | 225 ++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/schema.yml diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/schema.yml b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/schema.yml new file mode 100644 index 00000000..c81f12a2 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/schema.yml @@ -0,0 +1,225 @@ +version: 2 + +models: + + # TED DBT + - name: pf_tesouro + description: > + Esta tabela contém registros de Programações Financeiras extraídas do SIAFI, detalhando informações como tipo de programação, data de execução e valor. + A tabela é atualizada diariamente e reflete as autorizações de movimentação financeira no âmbito da execução orçamentária federal. + meta: + tags: + - bronze + columns: + - name: emissao_mes + description: > + Mês de emissão da Programação Financeira. + - name: emissao_dia + description: > + Dia de emissão da Programação Financeira. + - name: ug_emitente + description: > + Código da Unidade Gestora responsável pela emissão da Programação Financeira. + - name: ug_emitente_descricao + description: > + Nome ou descrição da Unidade Gestora emitente da Programação Financeira. + - name: ug_favorecido + description: > + Código da Unidade Gestora favorecida pela Programação Financeira. + - name: ug_favorecido_descricao + description: > + Nome ou descrição da Unidade Gestora favorecida pela Programação Financeira. + - name: pf_evento + description: > + Código do evento contábil associado à Programação Financeira, representando a natureza da transação. + - name: pf_evento_descricao + description: > + Descrição do evento contábil vinculado à Programação Financeira. + - name: pf + description: > + Número identificador único da Programação Financeira. + - name: pf_inscricao + description: > + Número de inscrição da Programação Financeira, utilizado para controle e acompanhamento. + - name: pf_acao + description: > + Código da ação orçamentária associada à Programação Financeira. + - name: pf_acao_descricao + description: > + Descrição da ação orçamentária vinculada à Programação Financeira. + - name: pf_fonte_recursos + description: > + Código da fonte de recursos associada à Programação Financeira, indicando a origem dos recursos utilizados. + - name: pf_fonte_recursos_descricao + description: > + Descrição textual da fonte de recursos vinculada à Programação Financeira. + - name: pf_vinculacao_pagamento + description: > + Código da vinculação de pagamento associada à Programação Financeira. + - name: pf_vinculacao_pagamento_descricao + description: > + Descrição da vinculação de pagamento vinculada à Programação Financeira. + - name: pf_categoria_gasto + description: > + Código da categoria de gasto associada à Programação Financeira, conforme classificação orçamentária. + - name: pf_recurso + description: > + Código do recurso associado à Programação Financeira. + - name: pf_recurso_descricao + description: > + Descrição do recurso vinculado à Programação Financeira. + - name: doc_observacao + description: > + Observações adicionais relacionadas à Programação Financeira. + - name: pf_valor_linha + description: > + Valor monetário individual da linha da Programação Financeira. + data_tests: + - verificacao_tipagem: + nome_tabela: 'ted.pf_tesouro' + nome_coluna: 'emissao_dia' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'ted.pf_tesouro' + nome_coluna: 'pf_valor_linha' + tipo_esperado: 'numeric' + + - name: nc_tesouro + description: > + Esta tabela registra informações detalhadas sobre as Notas de Crédito emitidas no âmbito da execução orçamentária e financeira do governo federal. + As Notas de Crédito representam autorizações para a realização de despesas, sendo fundamentais para o controle e acompanhamento da execução orçamentária. + meta: + tags: + - bronze + columns: + - name: emissao_mes + description: > + Mês de emissão da Nota de Crédito. + - name: emissao_dia + description: > + Dia de emissão da Nota de Crédito. + - name: nc + description: > + Número identificador único da Nota de Crédito. + - name: nc_transferencia + description: > + Indicador de que a Nota de Crédito refere-se a uma transferência de recursos entre unidades gestoras. + - name: nc_fonte_recursos + description: > + Código da fonte de recursos associada à Nota de Crédito, indicando a origem dos recursos utilizados. + - name: nc_fonte_recursos_descricao + description: > + Descrição textual da fonte de recursos vinculada à Nota de Crédito. + - name: ptres + description: > + Código do Plano Interno de Trabalho (PTRES) relacionado à Nota de Crédito, utilizado para detalhar a alocação dos recursos. + - name: nc_evento + description: > + Código do evento contábil associado à Nota de Crédito, representando a natureza da transação. + - name: nc_evento_descricao + description: > + Descrição do evento contábil vinculado à Nota de Crédito. + - name: ug_responsavel + description: > + Código da Unidade Gestora responsável pela emissão da Nota de Crédito. + - name: ug_responsavel_descricao + description: > + Nome ou descrição da Unidade Gestora responsável pela emissão da Nota de Crédito. + - name: natureza_despesa + description: > + Código da natureza da despesa associada à Nota de Crédito, conforme classificação orçamentária. + - name: natureza_despesa_detalhada + description: > + Descrição detalhada da natureza da despesa vinculada à Nota de Crédito. + - name: plano_interno + description: > + Código do plano interno relacionado à Nota de Crédito, utilizado para controle interno da execução orçamentária. + - name: plano_detalhado_descricao1 + description: > + Primeira descrição detalhada do plano interno associado à Nota de Crédito. + - name: plano_detalhado_descricao2 + description: > + Segunda descrição detalhada do plano interno associado à Nota de Crédito. + - name: favorecido_doc + description: > + Documento de identificação (CPF ou CNPJ) do favorecido pela Nota de Crédito. + - name: favorecido_doc_descricao + description: > + Nome ou razão social do favorecido identificado na Nota de Crédito. + - name: nc_valor_linha + description: > + Valor monetário individual da linha da Nota de Crédito. + - name: movimento_liquido + description: > + Valor líquido do movimento financeiro associado à Nota de Crédito, após deduções ou acréscimos aplicáveis. + + - name: planos_acao + description: > + Esta tabela armazena informações detalhadas sobre os Planos de Ação vinculados a programas públicos. + Ela consolida dados sobre as unidades envolvidas na execução, prazos de vigência, valores, justificativas, + formas de execução e instrumentos utilizados. Serve como base para o acompanhamento, análise e auditoria + da implementação de ações públicas em diferentes formatos de execução. + meta: + tags: + - bronze + columns: + - name: id_plano_acao + description: > + Identificador único do Plano de Ação. + - name: id_programa + description: > + Identificador único do programa ao qual o Plano de Ação está vinculado. + - name: sigla_unidade_descentralizada + description: > + Sigla da unidade descentralizada responsável pelo Plano de Ação. + - name: unidade_descentralizada + description: > + Nome completo da unidade descentralizada responsável pelo Plano de Ação. + - name: sigla_unidade_responsavel_execucao + description: > + Sigla da unidade responsável pela execução do Plano de Ação. + - name: unidade_responsavel_execucao + description: > + Nome completo da unidade responsável pela execução do Plano de Ação. + - name: vl_total_plano_acao + description: > + Valor total previsto para execução do Plano de Ação. + - name: dt_inicio_vigencia + description: > + Data de início da vigência do Plano de Ação. + - name: dt_fim_vigencia + description: > + Data final da vigência do Plano de Ação. + - name: tx_objeto_plano_acao + description: > + Descrição do objeto do Plano de Ação, informando seu propósito e escopo. + - name: tx_justificativa_plano_acao + description: > + Justificativa para a elaboração e execução do Plano de Ação. + - name: in_forma_execucao_direta + description: > + Indicador booleano que sinaliza se a execução do plano ocorrerá de forma direta. + - name: in_forma_execucao_particulares + description: > + Indicador booleano que sinaliza se a execução contará com a participação de particulares. + - name: in_forma_execucao_descentralizada + description: > + Indicador booleano que sinaliza se a execução será realizada de forma descentralizada. + - name: tx_situacao_plano_acao + description: > + Situação atual do Plano de Ação ( em elaboração, aprovado, em execução, concluído). + - name: aa_ano_plano_acao + description: > + Ano de referência do Plano de Ação. + - name: vl_beneficiario_especifico + description: > + Valor destinado especificamente a beneficiários definidos no Plano de Ação. + - name: vl_chamamento_publico + description: > + Valor previsto para execução por meio de chamamento público. + - name: sq_instrumento + description: > + Código sequencial do instrumento jurídico ou administrativo associado ao Plano de Ação. + - name: aa_instrumento + description: > + Ano de referência do instrumento associado ao Plano de Ação. From 086d9b78b908bcd54b90c274d041a5810bd8805c Mon Sep 17 00:00:00 2001 From: mat054 Date: Mon, 27 Oct 2025 15:21:40 -0300 Subject: [PATCH 161/317] feat(pessoas silver): adicionando schema das tabelas silver de pessoas --- .../ipea/models/pessoas_dbt/silver/schema.yml | 320 ++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/schema.yml diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/schema.yml b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/schema.yml new file mode 100644 index 00000000..350036a3 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/schema.yml @@ -0,0 +1,320 @@ +version: 2 + +models: + + + # Silver + - name: afastamento_consolidado + description: > + Tabela gold que consolida os registros de afastamento dos servidores, integrando as fontes + `dados_afastamento` e `afastamento_historico`, com enriquecimento de informações vindas de + `dados_pessoais` (nome do servidor) e `dados_funcionais` (função e unidade de exercício). + O processo inclui deduplicação inteligente priorizando os registros mais confiáveis, + utilizando lógica de ranking e partição por CPF e data de início do afastamento. + meta: + tags: + - silver + columns: + - name: adiantamento_salario_ferias + description: Indica se houve adiantamento de salário junto às férias no período do afastamento. + - name: ano_exercicio + description: Ano de exercício a que o afastamento se refere. + - name: dt_fim + description: Data de término do afastamento. + - name: dt_fim_aquisicao + description: Data final do período aquisitivo das férias relacionadas ao afastamento. + - name: dt_ini + description: Data de início do afastamento. + - name: dt_inicio_aquisicao + description: Data inicial do período aquisitivo de férias. + - name: dt_inicio_ferias_interrompidas + description: Data de início das férias interrompidas, se houver. + - name: dias_restantes + description: Quantidade de dias restantes de férias ou afastamento. + - name: gratificacao_natalina + description: Indica se houve gratificação natalina no período do afastamento. + - name: numero_parcela + description: Número da parcela relacionada ao afastamento. + - name: parcela_continuacao_interrupcao + description: Indica se o afastamento é continuação ou interrupção de uma parcela anterior. + - name: parcela_interrompida + description: Indica se a parcela foi interrompida. + - name: qtde_dias + description: Quantidade total de dias do afastamento. + - name: cpf + description: CPF do servidor. + - name: cod_diploma_afastamento + description: Código do diploma legal que ampara o afastamento. + - name: cod_ocorrencia + description: Código da ocorrência de afastamento. + - name: dt_publicacao_afastamento + description: Data de publicação do afastamento no diário oficial ou sistema correspondente. + - name: desc_diploma_afastamento + description: Descrição textual do diploma legal de afastamento. + - name: desc_ocorrencia + description: Descrição da ocorrência relacionada ao afastamento. + - name: numero_diploma_afastamento + description: Número do diploma legal do afastamento. + - name: gr_matricula + description: Número da matrícula do servidor no sistema GRH, quando disponível. + - name: origem_dados + description: Origem do dado de afastamento (`dados_afastamento` ou `afastamento_historico`). + - name: nome_pessoa + description: Nome completo do servidor, obtido a partir da tabela de dados pessoais. + - name: cod_funcao + description: Código da função de chefia ou cargo de confiança ocupado pelo servidor no momento do afastamento. + - name: sigla_uorg_exercicio + description: Sigla da unidade organizacional onde o servidor exercia suas funções. + + + + - name: quantitativo_alocados_ocupados + description: > + Tabela gold que consolida e compara os quantitativos de cargos ocupados e cargos previstos + em funções de chefia, correlacionando os dados dos sistemas SIAPE e SIORG. A lógica aplica + uma combinação heurística de códigos de função com base na estrutura dos códigos e siglas + das unidades organizacionais, com objetivo de identificar inconsistências, sobras ou + vacâncias entre as vagas disponíveis e as efetivamente ocupadas. + meta: + tags: + - silver + columns: + - name: codigo_siape + description: Código da função do servidor registrado no SIAPE (`cod_funcao`), representando funções ocupadas. + - name: codigo_siorg + description: Código da função de chefia conforme registrado no SIORG (`funcao`), representando vagas previstas. + - name: nomeunidade + description: Nome da unidade organizacional, obtido da base SIORG ou SIAPE (com prioridade para SIORG). + - name: siglaunidade + description: Sigla da unidade organizacional, unificada a partir das duas fontes (com prioridade para SIORG). + - name: nome_cargo + description: Nome ou denominação do cargo/função de chefia, com base em SIORG ou SIAPE. + - name: qtd_vagas_cargo + description: Quantidade de vagas previstas para o cargo de chefia na estrutura organizacional (dados do SIORG). + - name: qtd_vagas_ocupadas + description: Quantidade de cargos de chefia efetivamente ocupados, com base nos registros de servidores no SIAPE. + - name: qtd_cargos_vagos + description: > + Diferença entre o número de vagas previstas (`qtd_vagas_cargo`) e as ocupadas (`qtd_vagas_ocupadas`). + Indica o total de cargos vagos. Retorna `null` se a quantidade de vagas previstas não estiver disponível. + + + - name: servidores_detalhados + description: > + Tabela gold que consolida dados pessoais, funcionais, educacionais e organizacionais dos servidores. + Integra informações de múltiplas fontes do SIAPE, combinando registros de identificação, vínculo funcional, + escolaridade, endereço da unidade de exercício e metadados de transações. Essa visão é útil para análises + completas do perfil dos servidores ativos e inativos, permitindo estudos sociodemográficos, funcionais e + institucionais detalhados. + meta: + tags: + - silver + columns: + # Dados pessoais + - name: cpf + description: CPF do servidor, utilizado como chave primária de junção entre as bases. + - name: nome_pessoa + description: Nome completo do servidor. + - name: dt_nascimento + description: Data de nascimento do servidor. + - name: nome_cor + description: Cor/raça autodeclarada do servidor. + - name: nome_estado_civil + description: Estado civil declarado. + - name: nome_mae + description: Nome da mãe do servidor. + - name: nome_pai + description: Nome do pai do servidor. + - name: nome_municipio_nascimento + description: Município de nascimento do servidor. + - name: nome_nacionalidade + description: Nacionalidade declarada. + - name: nome_sexo + description: Sexo/gênero do servidor. + + # Dados funcionais + - name: matricula_siape + description: Matrícula SIAPE do servidor. + - name: nome_funcao + description: Nome da função de chefia ou cargo comissionado exercido, se houver. + - name: cod_funcao + description: Código da função exercida. + - name: nome_cargo + description: Nome do cargo efetivo. + - name: cod_cargo + description: Código do cargo efetivo. + - name: nome_jornada + description: Jornada de trabalho do servidor. + - name: dt_ingresso_funcao + description: Data de ingresso na função atual. + - name: nome_regime_juridico + description: Regime jurídico do vínculo funcional. + - name: nome_situacao_funcional + description: Situação funcional (ativo, cedido, aposentado etc). + - name: participa_pgd + description: Indica se o servidor participa do Programa de Gestão (teletrabalho). + + # Escolaridade e titulação + - name: nome_escolaridade_principal + description: Nível de escolaridade mais elevado do servidor. + - name: nome_titulacao_principal + description: Título acadêmico mais elevado do servidor ( mestrado, doutorado). + + # Unidade organizacional + - name: nome_uorg_exercicio + description: Nome da unidade organizacional onde o servidor exerce atualmente. + - name: sigla_uorg_exercicio + description: Sigla da unidade de exercício. + - name: nome_uorg_lotacao + description: Nome da unidade de lotação original. + - name: sigla_uorg_lotacao + description: Sigla da unidade de lotação. + - name: nome_orgao_funcional + description: Nome do órgão funcional ao qual o servidor está vinculado. + - name: sigla_orgao_funcional + description: Sigla do órgão funcional. + + # Endereço da unidade + - name: logradouro_uorg + description: Nome do logradouro da unidade. + - name: numero_endereco_uorg + description: Número do endereço da unidade. + - name: complemento_endereco_uorg + description: Complemento do endereço. + - name: bairro_uorg + description: Bairro onde a unidade está localizada. + - name: cep_uorg + description: CEP da unidade. + - name: nome_municipio_uorg + description: Município da unidade. + - name: uf_uorg + description: Unidade federativa da unidade. + + # Contatos institucionais + - name: email_institucional + description: Email institucional do servidor. + - name: email_servidor + description: Email alternativo do servidor. + - name: email_chefia_imediata + description: Email da chefia imediata. + - name: telefone_uorg + description: Telefone da unidade de exercício. + - name: fax_uorg + description: Fax da unidade de exercício. + + # Metadados e auditoria + - name: ident_unica_funcional + description: Identificador único funcional, usado para rastreamento interno. + - name: dt_ultima_transacao_servidor + description: Data da última transação registrada no sistema para o servidor. + + # Informações complementares + - name: cod_ocorr_aposentadoria + description: Código da ocorrência de aposentadoria (se houver). + - name: dt_ocorr_aposentadoria + description: Data da aposentadoria. + - name: cod_vale_transporte + description: Código do vale transporte. + - name: valor_vale_transporte + description: Valor mensal do vale transporte recebido. + - name: pontuacao_desempenho + description: Pontuação de desempenho do servidor, se aplicável. + + + + + - name: tabela_correlacao_cargos + description: > + Tabela final que correlaciona cargos entre os sistemas SIAPE e SIORG, utilizando + combinações de códigos e unidades organizacionais. Cada linha representa um servidor + com informações enriquecidas da função e da chefia, além da hierarquia do cargo e + dados pessoais básicos. Essa tabela é útil para analisar discrepâncias, estruturar + a hierarquia funcional e verificar se as funções atribuídas no SIAPE estão + corretamente refletidas no SIORG. + + meta: + tags: + - silver + columns: + - name: codigo_siape + description: Código da função no SIAPE. + - name: codigo_siorg + description: Código da função no SIORG. + - name: codigo_combinacao_siape + description: Código combinado utilizado para comparar cargos no SIAPE. + - name: codigo_combinacao_siorg + description: Código combinado utilizado para comparar cargos no SIORG. + - name: matricula_siape + description: Matrícula do servidor no sistema SIAPE. + - name: cpf + description: CPF do servidor titular do cargo. + - name: cpf_chefia_imediata + description: CPF da chefia imediata do servidor. + - name: cod_situacao_funcional + description: Código da situação funcional do servidor no SIAPE. + - name: nome_situacao_funcional + description: Descrição da situação funcional. + - name: hierarquia_cargo + description: Valor numérico que representa a posição hierárquica do cargo. Quanto menor, mais alto o cargo. + - name: servidor + description: Nome do servidor titular do cargo. + - name: dt_nascimento + description: Data de nascimento do servidor. + - name: nome_sexo + description: Sexo do servidor. + - name: nome_estado_civil + description: Estado civil do servidor. + - name: nome_nacionalidade + description: Nacionalidade do servidor. + - name: nome_cor + description: Cor/raça autodeclarada pelo servidor. + - name: uf_nascimento + description: Unidade Federativa (UF) de nascimento. + - name: nome_municipio_nascimento + description: Município de nascimento do servidor. + - name: nome_chefia + description: Nome da chefia imediata, obtido pelo CPF da chefia. + - name: codigounidade + description: Código da unidade organizacional onde o cargo está lotado. + - name: codigounidadepai + description: Código da unidade organizacional imediatamente superior. + - name: caminho_unidade + description: Caminho hierárquico da unidade organizacional, representando sua posição na estrutura. + - name: ordem_grandeza + description: Nível de profundidade da unidade na hierarquia institucional. + - name: nomeunidade + description: Nome da unidade organizacional. + - name: siglaunidade + description: Sigla da unidade organizacional. + - name: nome_cargo + description: Denominação do cargo ocupado, conforme SIAPE ou SIORG. + - name: servidores_carreira + description: Classificação se o cargo é de carreira ou nomeação livre. + + + - name: unidades_organizacionais_siorg_siape + description: > + Tabela que correlaciona as unidades organizacionais do SIAPE e do SIORG, + com base na correspondência entre os códigos e siglas das unidades. + Essa tabela é utilizada para identificar quais unidades estão presentes + em ambos os sistemas, apenas no SIAPE ou apenas no SIORG. + + meta: + tags: + - silver + columns: + - name: nome_unidade + description: Nome da unidade organizacional, proveniente do SIAPE ou SIORG. + - name: sigla_uorg + description: Sigla da unidade organizacional. Pode vir do SIAPE (dados_uorg) ou do SIORG (unidade_organizacional). + - name: codigo_unidade_siape + description: Código da unidade organizacional segundo o SIAPE (extraído de dados_uorg). + - name: codigo_unidade_siorg + description: Código da unidade organizacional segundo o SIORG (coluna codigounidade). + - name: tipo_correlacao + description: > + Tipo de correspondência encontrada entre os sistemas: + - "ambos": unidade presente no SIAPE e SIORG. + - "apenas_siorg": presente apenas no SIORG. + - "apenas_siape": presente apenas no SIAPE. + From 900afb198ec79ec0695eb8e58b829045803d5b6a Mon Sep 17 00:00:00 2001 From: mat054 Date: Mon, 27 Oct 2025 15:22:16 -0300 Subject: [PATCH 162/317] feat(pessoas gold): adicionando schema das tabelas gold de pessoas --- .../ipea/models/pessoas_dbt/gold/schema.yml | 199 ++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/schema.yml diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/schema.yml b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/schema.yml new file mode 100644 index 00000000..646e78af --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/schema.yml @@ -0,0 +1,199 @@ +version: 2 + +models: + + # Gold + - name: aposentadorias_resumo + description: > + Tabela de resumo dos servidores aposentados, contendo informações detalhadas + sobre a data de ingresso no serviço público, data de aposentadoria e tempo + de serviço público calculado em anos, meses e dias. A granularidade da tabela + é por CPF, ou seja, cada linha representa um servidor aposentado único. + + meta: + tags: + - gold + columns: + - name: cpf + description: Número do CPF do servidor aposentado. + - name: nome_pessoa + description: Nome completo do servidor aposentado. + - name: dt_ocorr_ingresso_serv_publico + description: Data de ingresso do servidor no serviço público. + - name: dt_ocorr_aposentadoria + description: Data de aposentadoria do servidor. + - name: mes_aposentadoria + description: Mês da aposentadoria (arredondado para o primeiro dia do mês). + - name: nome_situacao_funcional + description: Situação funcional do servidor no momento da aposentadoria. + - name: nome_ocorr_aposentadoria + description: Tipo de ocorrência que levou à aposentadoria do servidor. + - name: nome_cargo + description: Cargo ocupado pelo servidor no momento da aposentadoria. + - name: sigla_nivel_cargo + description: Sigla do nível do cargo ocupado. + - name: classe_padrao + description: Classe e padrão do cargo, no formato "classe-padrão". + - name: age + description: Diferença completa entre as datas de ingresso e aposentadoria, em formato de intervalo. + - name: diff_anos + description: Quantidade de anos entre o ingresso no serviço público e a aposentadoria. + - name: diff_meses + description: Quantidade de meses entre o ingresso e a aposentadoria (ignora anos completos). + - name: diff_dias + description: Quantidade de dias entre o ingresso e a aposentadoria (ignora anos e meses completos). + + + + - name: cargos_consolidado + description: > + Tabela que consolida informações de cargos ocupados e vagos no serviço público, + a partir da junção entre os dados do SIAPE (servidores ativos e detalhados) e + os dados do SIORG (estrutura de cargos disponíveis). Essa junção é feita com + base no CPF do titular do cargo. Quando os campos do SIAPE estão nulos, o cargo + provavelmente está vago. A tabela pode ser utilizada para identificar cargos + vagos por unidade organizacional. + + meta: + tags: + - gold + columns: + - name: siorg_cod_unidade + description: Código da unidade organizacional no SIORG. + - name: siorg_nome_unidade + description: Nome da unidade organizacional no SIORG. + - name: siorg_sigla_unidade + description: Sigla da unidade organizacional no SIORG. + - name: siorg_municipio_unidade + description: Município da unidade organizacional no SIORG. + - name: siorg_uf_unidade + description: Unidade federativa (UF) da unidade organizacional no SIORG. + - name: siorg_denominacao_cargo + description: Denominação do cargo conforme registrado no SIORG. + - name: siorg_funcao + description: Função comissionada associada ao cargo no SIORG. + - name: siorg_cod_instancia_cargo + description: Código da instância do cargo no SIORG. + - name: siorg_cpf_titular + description: CPF do titular do cargo segundo o SIORG. + - name: siorg_nome_titular + description: Nome do titular do cargo segundo o SIORG. + + - name: siape_cpf + description: CPF do servidor conforme os dados do SIAPE. + - name: siape_nome_pessoa + description: Nome completo do servidor no SIAPE. + - name: siape_nome_cargo_efetivo + description: Nome do cargo efetivo ocupado pelo servidor no SIAPE. + - name: siape_nome_funcao_comissionada + description: Nome da função comissionada ocupada pelo servidor no SIAPE. + - name: siape_cod_uorg + description: Código da unidade de exercício do servidor no SIAPE. + - name: siape_nome_uorg + description: Nome da unidade de exercício do servidor no SIAPE. + - name: siape_sigla_uorg + description: Sigla da unidade de exercício do servidor no SIAPE. + - name: siape_uf_uorg + description: Unidade federativa (UF) da unidade de exercício no SIAPE. + - name: siape_situacao_funcional + description: Situação funcional atual do servidor segundo o SIAPE. + + + + - name: hierarquia + description: > + Tabela que correlaciona dados do SIAPE (dados funcionais e pessoais dos servidores) + com a estrutura organizacional do SIORG, atribuindo uma métrica de hierarquia aos cargos + com base na codificação da função. A hierarquia é determinada por uma fórmula baseada + na categoria e no nível do cargo. A tabela também indica se o cargo é de carreira ou de + nomeação livre, além de consolidar informações sobre o servidor e sua chefia imediata. + + meta: + tags: + - gold + columns: + - name: codigo_siape + description: Código da função no SIAPE. + - name: codigo_siorg + description: Código da função no SIORG, com espaços removidos. + - name: codigo_combinacao_siape + description: Código combinado derivado da função no SIAPE, usado para correlação. + - name: codigo_combinacao_siorg + description: Código combinado derivado da função no SIORG, usado para correlação. + - name: matricula_siape + description: Matrícula funcional do servidor no SIAPE. + - name: cpf + description: CPF do servidor. + - name: cpf_chefia_imediata + description: CPF da chefia imediata do servidor. + - name: cod_situacao_funcional + description: Código da situação funcional do servidor. + - name: nome_situacao_funcional + description: Descrição da situação funcional do servidor. + - name: hierarquia_cargo + description: Indicador numérico da hierarquia do cargo (quanto menor, maior o cargo). + - name: servidor + description: Nome do servidor ocupante do cargo. + - name: dt_nascimento + description: Data de nascimento do servidor. + - name: nome_sexo + description: Sexo do servidor. + - name: nome_estado_civil + description: Estado civil do servidor. + - name: nome_nacionalidade + description: Nacionalidade do servidor. + - name: nome_cor + description: Cor/raça do servidor. + - name: uf_nascimento + description: Unidade federativa de nascimento do servidor. + - name: nome_municipio_nascimento + description: Município de nascimento do servidor. + - name: nome_chefia + description: Nome da chefia imediata do servidor. + - name: codigounidade + description: Código da unidade organizacional associada ao cargo. + - name: codigounidadepai + description: Código da unidade organizacional pai (superior). + - name: caminho_unidade + description: Caminho hierárquico completo da unidade. + - name: ordem_grandeza + description: Nível hierárquico da unidade na estrutura organizacional. + - name: nomeunidade + description: Nome da unidade organizacional. + - name: siglaunidade + description: Sigla da unidade organizacional. + - name: nome_cargo + description: Nome ou denominação do cargo ocupado. + - name: servidores_carreira + description: Indica se o servidor está em cargo de carreira ou em nomeação livre. + + + - name: resumo_quadro_pessoal + description: > + Tabela resumo com a distribuição dos servidores públicos por cargo efetivo, gênero, + situação funcional e unidade federativa da unidade de exercício (UF). A granularidade é agregada, + e cada linha representa uma combinação única desses atributos, com a respectiva contagem de servidores. + + meta: + tags: + - gold + columns: + - name: cargo_efetivo + description: > + Nome do cargo efetivo ocupado pelo servidor. Caso não informado, é preenchido com 'N/A'. + - name: genero + description: > + Gênero do servidor (masculino, feminino ou outro), conforme informado no cadastro pessoal. + Em casos de ausência de informação, é preenchido com 'N/A'. + - name: situacao_funcional + description: > + Situação funcional atual do servidor ( Ativo, Aposentado, Cedido, etc). + Valores ausentes são substituídos por 'N/A'. + - name: localidade_uf + description: > + Unidade Federativa (UF) da unidade organizacional onde o servidor exerce suas funções. + Valores ausentes são preenchidos com 'N/A'. + - name: quantidade_servidores + description: > + Quantidade total de servidores distintos (com base no CPF) que se enquadram na combinação dos atributos anteriores. + From 5031426a10ddcd5878a1c6e86040348a77c0e5c1 Mon Sep 17 00:00:00 2001 From: mat054 Date: Mon, 27 Oct 2025 15:25:47 -0300 Subject: [PATCH 163/317] feat(pessoas bronze): adicionando schema das tabelas bronze de pessoas --- .../ipea/models/pessoas_dbt/bronze/schema.yml | 867 ++++++++++++++++++ 1 file changed, 867 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/schema.yml diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/schema.yml b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/schema.yml new file mode 100644 index 00000000..52834732 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/schema.yml @@ -0,0 +1,867 @@ +version: 2 + +models: + + # Pessoas DBT + + ## Bronze + - name: afastamento_historico + description: > + Tabela bronze que armazena o histórico de afastamentos dos servidores. + Contém informações detalhadas sobre cada período de afastamento, incluindo datas, tipo, e amparo legal. + Os dados são limpos e padronizados a partir da fonte original. + meta: + tags: + - bronze + columns: + - name: adiantamento_salario_ferias + description: "Indica se houve adiantamento de salário durante as férias." + - name: ano_exercicio + description: "Ano de exercício a que o afastamento se refere." + - name: dt_fim + description: "Data de término do afastamento." + - name: dt_fim_aquisicao + description: "Data final do período aquisitivo de férias." + - name: dt_ini + description: "Data de início do afastamento." + - name: dt_inicio_aquisicao + description: "Data inicial do período aquisitivo de férias." + - name: dt_inicio_ferias_interrompidas + description: "Data de início de férias que foram interrompidas." + - name: dias_restantes + description: "Dias restantes de afastamento." + - name: gratificacao_natalina + description: "Indica se o afastamento está relacionado à gratificação natalina." + - name: numero_parcela + description: "Número da parcela do afastamento." + - name: parcela_continuacao_interrupcao + description: "Indica se a parcela é uma continuação ou interrupção." + - name: parcelainterrompida + description: "Indica se a parcela foi interrompida." + - name: qtde_dias + description: "Quantidade de dias do afastamento." + - name: cpf + description: "CPF do servidor." + - name: cod_diploma_afastamento + description: "Código do diploma legal do afastamento." + - name: cod_ocorrencia + description: "Código da ocorrência do afastamento." + - name: dt_publicacao_afastamento + description: "Data de publicação do afastamento." + - name: desc_diploma_afastamento + description: "Descrição do diploma legal do afastamento." + - name: desc_ocorrencia + description: "Descrição da ocorrência do afastamento." + - name: numero_diploma_afastamento + description: "Número do diploma legal do afastamento." + + + - name: cargos_funcoes + description: > + Tabela bronze que representa as funções e cargos extraídos do sistema SIORG. + Ela contém informações sobre o código e nome do cargo/função, nível hierárquico, + categoria funcional, regras de autoridade, além de dados normativos (ato normativo) + e as diversas denominações associadas a cada cargo/função. + Os dados são expandidos a partir de arrays JSON para facilitar a análise. + meta: + tags: + - bronze + columns: + - name: codigotipo + description: "Código que representa o tipo do cargo ou função." + + - name: nome + description: "Nome completo do cargo ou função." + + - name: sigla + description: "Sigla identificadora do cargo ou função." + + - name: codigocargofuncao + description: "Código único que identifica o cargo ou função." + + - name: categoria + description: "Categoria funcional do cargo, como direção, assessoramento, etc." + + - name: nivel + description: "Nível hierárquico do cargo ou função." + + - name: atonormativo__tipoato + description: "Tipo do ato normativo que criou ou regulamenta o cargo/função." + + - name: atonormativo__codigounidade + description: "Código da unidade responsável pelo ato normativo." + + - name: atonormativo__numero + description: "Número do ato normativo relacionado ao cargo ou função." + + - name: atonormativo__dataassinatura + description: "Data em que o ato normativo foi assinado." + + - name: atonormativo__datapublicacao + description: "Data de publicação do ato normativo no diário oficial." + + - name: atonormativo__datavigencia + description: "Data em que o ato normativo passou a vigorar." + + - name: atonormativo__ementa + description: "Ementa ou resumo descritivo do ato normativo." + + - name: atonormativo__url + description: "URL de acesso ao conteúdo completo do ato normativo." + + - name: atonormativo__codigotipo + description: "Código que representa o tipo de ato normativo." + + - name: atonormativo__siglatipo + description: "Sigla que representa o tipo de ato normativo." + + - name: denominacao_codigo + description: "Código individual da denominação expandida a partir do array JSON." + + - name: denominacao_descricao + description: "Descrição da denominação expandida para o cargo/função." + + + - name: dados_afastamento + description: > + Tabela bronze com dados atuais de afastamentos dos servidores. + meta: + tags: + - bronze + columns: + - name: adiantamento_salario_ferias + description: "Indica se houve adiantamento de salário durante as férias." + - name: ano_exercicio + description: "Ano de exercício a que o afastamento se refere." + - name: cod_diploma_afastamento + description: "Código do diploma legal do afastamento." + - name: cod_ocorrencia + description: "Código da ocorrência do afastamento." + - name: desc_diploma_afastamento + description: "Descrição do diploma legal do afastamento." + - name: desc_ocorrencia + description: "Descrição da ocorrência do afastamento." + - name: dt_fim + description: "Data de término do afastamento." + - name: dt_ini + description: "Data de início do afastamento." + - name: dt_inicio_aquisicao + description: "Data inicial do período aquisitivo de férias." + - name: dt_publicacao_afastamento + description: "Data de publicação do afastamento." + - name: dias_restantes + description: "Dias restantes de afastamento." + - name: gratificacao_natalina + description: "Indica se o afastamento está relacionado à gratificação natalina." + - name: gr_matricula + description: "Matrícula GR." + - name: numero_diploma_afastamento + description: "Número do diploma legal do afastamento." + - name: numero_parcela + description: "Número da parcela do afastamento." + - name: parcela_continuacao_interrupcao + description: "Indica se a parcela é uma continuação ou interrupção." + - name: qtde_dias + description: "Quantidade de dias do afastamento." + - name: cpf + description: "CPF do servidor." + - name: dt_fim_aquisicao + description: "Data final do período aquisitivo de férias." + + + - name: dados_curriculo + description: > + Tabela bronze que representa dados curriculares e experiências profissionais dos servidores públicos. + Inclui informações sobre cursos realizados, instituições de ensino, tempo de experiência, projetos desenvolvidos e vínculos com órgãos ou empresas. + As datas foram padronizadas para o primeiro dia do mês (`dt_mes_`) e os dados textuais foram limpos de caracteres inválidos. + meta: + tags: + - bronze + columns: + - name: cpf + description: "CPF do servidor, formatado apenas com números." + + - name: ident_unica + description: "Identificador único do servidor no sistema, usado para correlacionar registros." + + - name: codigo_experiencia + description: "Código associado ao tipo de experiência do servidor ( 1 para formação, 2 para experiência profissional)." + + - name: cod_curso + description: "Código do curso realizado, se disponível." + + - name: nome_curso + description: "Nome do curso de formação acadêmica ou técnica. Exemplo: 'Ciências Contábeis', 'Administração'." + + - name: dt_mes_conclusao + description: "Data de conclusão do curso, normalizada para o primeiro dia do mês. Exemplo: '1991-12-01'." + + - name: nome_instituicao + description: "Nome da instituição onde o curso foi realizado. Exemplo: 'UDF', 'Universidade Católica de Brasília'." + + - name: nome_area_experiencia + description: > + Grau ou situação da experiência/capacitação. Pode indicar o nível ou status como: + 'Concluído', 'Intermediário', 'Básico', 'Curso', 'Avançado'." + + - name: carga_horaria + description: "Carga horária do curso, se disponível. Armazenado como texto." + + - name: nome_cargo + description: "Nome do cargo ou função exercida durante a experiência profissional." + + - name: dt_mes_inicio + description: "Data de início da experiência profissional, no formato 'YYYY-MM-01'." + + - name: nome_orgao_empresa + description: "Nome da organização, empresa ou órgão público em que a experiência foi realizada." + + - name: dt_mes_fim + description: "Data de término da experiência profissional, normalizada para o primeiro dia do mês." + + - name: descricao_projeto + description: "Descrição de projetos relevantes associados à experiência." + + - name: informacoes_adicionais + description: "Informações complementares que detalham a experiência ou formação." + + - name: tipo_descricao + description: "Tipo da descrição curricular. Pode indicar se o registro refere-se a curso, experiência, projeto etc." + + + - name: dados_dependentes + description: > + Tabela bronze que contém informações sobre os dependentes dos servidores públicos. + Inclui grau de parentesco, condições de dependência, benefícios associados e o período em que a dependência esteve ativa. + Os dados foram tratados para remover valores inválidos como 'NaN' e '00000000', e datas foram convertidas para o formato `DATE`. + meta: + tags: + - bronze + columns: + - name: cod_condicao + description: > + Código que representa a condição da dependência ( dependente econômico, legal, etc.). + Usado para classificar o tipo de vínculo do dependente com o servidor. + + - name: cod_grau_parentesco + description: > + Código que indica o grau de parentesco do dependente com o servidor ( filho, cônjuge, pai, etc.). + + - name: cod_orgao + description: "Código do órgão ao qual o servidor está vinculado." + + - name: cpf + description: "CPF do servidor titular da matrícula, contendo apenas números." + + - name: matricula + description: "Número da matrícula funcional do servidor ao qual o dependente está associado." + + - name: nome_dependente + description: "Nome completo do dependente." + + - name: nome_condicao + description: > + Descrição da condição do dependente ( 'Dependente para IR', 'Dependente para Plano de Saúde'). + + - name: nome_grau_parentesco + description: > + Descrição textual do grau de parentesco entre o servidor e o dependente ( 'Filho', 'Cônjuge'). + + - name: cod_beneficio + description: "Código que representa o tipo de benefício relacionado ao dependente, se houver." + + - name: dt_fim + description: > + Data de término da condição de dependência, no formato 'DDMMYYYY', convertida para DATE. + Pode ser nula se a dependência ainda estiver ativa. + + - name: dt_inicio + description: > + Data de início da condição de dependência, no formato 'DDMMYYYY', convertida para DATE. + + - name: nome_beneficio + description: > + Nome do benefício associado ao dependente ( auxílio-saúde, plano de assistência etc.). + + + - name: dados_escolares + description: > + Tabela bronze que armazena dados sobre a formação educacional dos servidores públicos. + Inclui informações sobre escolaridade, titulação e cursos associados às matrículas funcionais. + Os dados foram limpos para remover valores vazios e o CPF foi padronizado contendo apenas dígitos. + meta: + tags: + - bronze + columns: + - name: cod_curso + description: "Código identificador do curso realizado pelo servidor." + + - name: nome_curso + description: "Nome do curso relacionado à formação do servidor." + + - name: cod_matricula + description: "Código da matrícula funcional do servidor, vinculado ao curso." + + - name: cod_orgao + description: "Código do órgão público ao qual o servidor está vinculado." + + - name: cod_titulacao + description: "Código que representa a titulação do servidor ( especialização, mestrado, doutorado)." + + - name: nome_titulacao + description: "Descrição textual da titulação ( 'Mestrado', 'Doutorado')." + + - name: cod_escolaridade + description: "Código do nível de escolaridade ( ensino médio, superior, técnico)." + + - name: nome_escolaridade + description: "Descrição do nível de escolaridade do servidor." + + - name: cpf + description: "CPF do servidor, contendo apenas números, utilizado para identificação única." + + + + - name: dados_financeiros + description: > + Tabela bronze que armazena dados financeiros associados a servidores públicos. + Contém informações detalhadas sobre rubricas (proventos e descontos), valores pagos, + períodos de referência e data de pagamento. Os dados passam por limpeza e transformação + de formatos monetários e temporais para garantir consistência e padronização. + meta: + tags: + - bronze + columns: + - name: cod_rubrica + description: "Código da rubrica financeira (provento ou desconto) referente ao pagamento do servidor." + + - name: indicador_rd + description: "Indicador que diferencia se a rubrica é de receita (R) ou despesa (D)." + + - name: nome_rubrica + description: "Nome descritivo da rubrica, como 'Salário Base', 'Auxílio Alimentação', etc." + + - name: numero_sequencia + description: "Número sequencial que identifica a ordem da rubrica na folha de pagamento." + + - name: valor_rubrica + description: > + Valor monetário da rubrica, convertido de string para tipo numérico. + Foi realizada limpeza dos pontos e substituição da vírgula decimal por ponto. + + - name: data_anomes_rubrica + description: > + Data correspondente ao mês e ano de referência da rubrica, convertida do formato textual ( 'JAN2020') para uma data no formato YYYY-MM-DD + com o dia fixado em 01. + + - name: prazo_rubrica + description: "Informação de prazo da rubrica (campo opcional e com dados variados, pode indicar vencimento ou parcelamento)." + + - name: mes_ano_pagamento + description: > + Data correspondente ao mês e ano de efetivação do pagamento, convertida do formato textual ( 'JAN2020') para data com o dia fixado em 01. + + - name: cpf + description: > + CPF do servidor, padronizado contendo apenas dígitos. Utilizado para vinculação com outras informações do servidor. + + - name: indicador_mov_supl + description: > + Indicador que mostra se a movimentação financeira se refere a um pagamento suplementar ou retroativo (campo técnico da folha). + + - name: periodo_rubrica + description: > + Período a que se refere a rubrica, podendo representar um agrupamento ou classificação contábil adicional." + + + - name: dados_funcionais + description: > + Tabela bronze que armazena os dados funcionais dos servidores, contendo informações + sobre cargos, funções, regimes jurídicos, ocorrências de ingresso e aposentadoria, e dados + de unidades organizacionais (lotação e exercício). As datas são padronizadas, os CPFs higienizados + e os valores tratados para garantir integridade e legibilidade dos dados. + meta: + tags: + - bronze + columns: + - name: cod_atividade_funcao + description: "Código da atividade ou função exercida pelo servidor." + + - name: cod_funcao + description: "Código identificador da função do servidor." + + - name: cod_jornada + description: "Código da jornada de trabalho do servidor." + + - name: cod_ocorr_ingresso_orgao + description: "Código da ocorrência referente ao ingresso no órgão." + + - name: cod_ocorr_ingresso_serv_publico + description: "Código da ocorrência de ingresso no serviço público federal." + + - name: cod_orgao + description: "Código do órgão atual de lotação ou exercício do servidor." + + - name: cod_padrao + description: "Código do padrão do cargo ocupado." + + - name: cod_situacao_funcional + description: "Código representando a situação funcional atual do servidor (ativo, afastado, etc.)." + + - name: cod_uorg_exercicio + description: "Código da unidade organizacional onde o servidor exerce suas atividades." + + - name: cod_upag + description: "Código da Unidade Pagadora responsável pelo pagamento ao servidor." + + - name: cod_orgao_origem + description: "Código do órgão de origem, em caso de movimentação funcional." + + - name: cpf_chefia_imediata + description: "CPF da chefia imediata, higienizado para conter apenas dígitos." + + - name: dt_exercicio_no_orgao + description: "Data em que o servidor iniciou o exercício no órgão atual." + + - name: dt_fim_vale_ar + description: "Data final da vigência do vale de auxílio-refeição." + + - name: dt_ingresso_funcao + description: "Data de ingresso na função atual." + + - name: dt_ocorr_ingresso_orgao + description: "Data da ocorrência de ingresso no órgão atual." + + - name: dt_ocorr_ingresso_serv_publico + description: "Data da ocorrência de ingresso no serviço público." + + - name: email_chefia_imediata + description: "E-mail institucional da chefia imediata, padronizado em minúsculo." + + - name: email_institucional + description: "E-mail institucional do servidor, padronizado em minúsculo." + + - name: email_servidor + description: "E-mail pessoal do servidor, padronizado em minúsculo." + + - name: ident_unica + description: "Identificação única do servidor no sistema." + + - name: matricula_siape + description: "Matrícula do servidor no sistema SIAPE." + + - name: modalidade_pgd + description: "Modalidade de participação no Programa de Gestão e Desempenho (PGD)." + + - name: nome_atividade_funcao + description: "Descrição textual da atividade ou função exercida." + + - name: nome_chefe_uorg + description: "Nome da chefia imediata da unidade organizacional." + + - name: nome_funcao + description: "Nome da função ocupada." + + - name: nome_jornada + description: "Descrição da jornada de trabalho." + + - name: nome_ocorr_ingresso_orgao + description: "Descrição da ocorrência de ingresso no órgão." + + - name: nome_ocorr_ingresso_serv_publico + description: "Descrição da ocorrência de ingresso no serviço público." + + - name: nome_orgao + description: "Nome do órgão onde o servidor está vinculado." + + - name: nome_regime_juridico + description: "Nome do regime jurídico do servidor ( Estatutário, CLT)." + + - name: nome_situacao_funcional + description: "Descrição da situação funcional do servidor." + + - name: nome_uorg_exercicio + description: "Nome da unidade organizacional de exercício." + + - name: nome_upag + description: "Nome da unidade pagadora responsável pelo servidor." + + - name: participa_pgd + description: "Indica se o servidor participa do Programa de Gestão e Desempenho." + + - name: percentual_ts + description: "Percentual de tempo de trabalho remoto (teletrabalho), convertido para valor numérico ( 0.75 representa 75%)." + + - name: sigla_orgao + description: "Sigla do órgão atual." + + - name: sigla_orgao_origem + description: "Sigla do órgão de origem." + + - name: sigla_regime_juridico + description: "Sigla do regime jurídico do servidor." + + - name: sigla_uorg_exercicio + description: "Sigla da unidade de exercício." + + - name: sigla_upag + description: "Sigla da unidade pagadora." + + - name: cpf + description: "CPF do servidor, com apenas dígitos." + + - name: cod_cargo + description: "Código do cargo efetivo ocupado pelo servidor." + + - name: cod_classe + description: "Código da classe funcional do cargo." + + - name: cod_ocorr_aposentadoria + description: "Código da ocorrência de aposentadoria." + + - name: dt_ini_vale_ar + description: "Data de início do benefício de auxílio-refeição." + + - name: dt_ocorr_aposentadoria + description: "Data da ocorrência de aposentadoria." + + - name: nome_cargo + description: "Nome do cargo efetivo." + + - name: nome_classe + description: "Nome da classe funcional do cargo." + + - name: nome_ocorr_aposentadoria + description: "Descrição da ocorrência de aposentadoria." + + - name: sigla_nivel_cargo + description: "Sigla do nível do cargo ocupado." + + - name: tipo_vale_ar + description: "Tipo de auxílio-refeição concedido." + + - name: cod_ocorr_isencao_ir + description: "Código da ocorrência de isenção de imposto de renda." + + - name: dt_ini_ocorr_isencao_ir + description: "Data de início da ocorrência de isenção de IR." + + - name: nome_ocorr_isencao_ir + description: "Descrição da ocorrência de isenção de imposto de renda." + + - name: cod_uorg_lotacao + description: "Código da unidade de lotação do servidor." + + - name: nome_uorg_lotacao + description: "Nome da unidade de lotação do servidor." + + - name: sigla_uorg_lotacao + description: "Sigla da unidade de lotação do servidor." + + - name: dt_fim_ocorr_isencao_ir + description: "Data de término da isenção de IR." + + - name: cod_ocorr_exclusao + description: "Código da ocorrência de exclusão do servidor do sistema." + + - name: dt_ocorr_exclusao + description: "Data da ocorrência de exclusão do servidor." + + - name: nome_ocorr_exclusao + description: "Descrição da ocorrência de exclusão do servidor." + + - name: dt_uorg_lotacao + description: "Data da lotação na unidade organizacional atual." + + - name: cod_vale_transporte + description: "Código do benefício de vale transporte." + + - name: valor_vale_transporte + description: "Valor monetário do vale transporte." + + - name: dt_uorg_exercicio + description: "Data de início do exercício na unidade organizacional atual." + + - name: pontuacao_desempenho + description: > + Pontuação atribuída ao servidor conforme avaliação de desempenho. + Pode conter letras (A, B, C) ou valores numéricos, depende da origem. Mantido como string. + + + - name: dados_pa + description: > + Tabela bronze com dados de pensionistas e alimentados. + meta: + tags: + - bronze + columns: + - name: agencia_beneficiario + description: "Agência bancária do beneficiário." + - name: banco_beneficiario + description: "Banco do beneficiário." + - name: cod_orgao + description: "Código do órgão." + - name: conta_beneficiario + description: "Conta bancária do beneficiário." + - name: cpf_beneficiario + description: "CPF do beneficiário." + - name: matricula_servidor + description: "Matrícula do servidor instituidor da pensão." + - name: nome_beneficiario + description: "Nome do beneficiário." + - name: valor_ultima_pensao + description: "Valor da última pensão." + - name: cpf_servidor + description: "CPF do servidor instituidor da pensão." + - name: cod_vinculo_servidor + description: "Código do vínculo com o servidor." + - name: nome_alimentado + description: "Nome do alimentado." + - name: nome_vinculo_servidor + description: "Nome do vínculo com o servidor." + + - name: dados_pessoais + description: > + Tabela bronze com dados pessoais dos servidores. + meta: + tags: + - bronze + columns: + - name: cod_cor + description: "Código da cor/raça." + - name: cod_estado_civil + description: "Código do estado civil." + - name: cod_nacionalidade + description: "Código da nacionalidade." + - name: cod_sexo + description: "Código do sexo." + - name: dt_nascimento + description: "Data de nascimento." + - name: grupo_sanguineo + description: "Grupo sanguíneo." + - name: nome_pessoa + description: "Nome da pessoa." + - name: nome_cor + description: "Nome da cor/raça." + - name: nome_estado_civil + description: "Nome do estado civil." + - name: nome_mae + description: "Nome da mãe." + - name: nome_municipio_nascimento + description: "Nome do município de nascimento." + - name: nome_nacionalidade + description: "Nome da nacionalidade." + - name: nome_pai + description: "Nome do pai." + - name: nome_sexo + description: "Nome do sexo." + - name: num_pispasep + description: "Número do PIS/PASEP." + - name: uf_nascimento + description: "UF de nascimento." + - name: cpf + description: "CPF do servidor." + - name: cod_deficiencia_fisica + description: "Código de deficiência física." + - name: nome_deficiencia_fisica + description: "Nome da deficiência física." + - name: dt_chegada_brasil + description: "Data de chegada ao Brasil." + - name: nome_pais_origem + description: "Nome do país de origem." + + - name: dados_uorg + description: > + Tabela bronze com dados das Unidades Organizacionais (UORGs). + meta: + tags: + - bronze + columns: + - name: bairro_uorg + description: "Bairro da UORG." + - name: cep_uorg + description: "CEP da UORG." + - name: codigo_matricula + description: "Código de matrícula." + - name: codigo_municipio_uorg + description: "Código do município da UORG." + - name: codigo_orgao + description: "Código do órgão." + - name: codigo_orgao_uorg + description: "Código da UORG no órgão." + - name: email_uorg + description: "Email da UORG." + - name: tipo_endereco_uorg + description: "Tipo de endereço da UORG." + - name: logradouro_uorg + description: "Logradouro da UORG." + - name: nome_municipio_uorg + description: "Nome do município da UORG." + - name: nome_uorg + description: "Nome da UORG." + - name: telefone_uorg + description: "Telefone da UORG." + - name: numero_endereco_uorg + description: "Número do endereço da UORG." + - name: sigla_uorg + description: "Sigla da UORG." + - name: uf_uorg + description: "UF da UORG." + - name: cpf + description: "CPF associado à UORG." + - name: complemento_endereco_uorg + description: "Complemento do endereço da UORG." + - name: fax_uorg + description: "Fax da UORG." + + + - name: estrutura_organizacional_cargos + description: > + Tabela gold contendo a estrutura organizacional de unidades com informações expandidas de cargos e instâncias. + Os dados foram transformados para extrair e normalizar os campos aninhados no JSON armazenado na coluna `cargos`. + A tabela inclui apenas uma instância por código, priorizando a de maior ordem de grandeza. + meta: + tags: + - bronze + columns: + - name: codigounidade + description: Código da unidade organizacional. + - name: nomeunidade + description: Nome completo da unidade organizacional. + - name: siglaunidade + description: Sigla da unidade organizacional. + - name: municipio + description: Município onde a unidade está localizada. + - name: uf + description: Unidade federativa (UF) correspondente ao município. + - name: denominacao + description: Denominação do cargo. + - name: funcao + description: Função associada à denominação do cargo. + - name: codigo_instancia + description: Código da instância do cargo na estrutura. + - name: nome_titular + description: Nome do servidor titular do cargo. + - name: cpf_titular + description: CPF do servidor titular do cargo (com apenas dígitos numéricos). + - name: ordem_grandeza + description: Nível hierárquico da unidade na estrutura, utilizado para escolher a instância mais relevante. + + + - name: lista_servidores + description: > + Tabela bronze com a lista de servidores e suas UORGs. + meta: + tags: + - bronze + columns: + - name: cod_uorg + description: "Código da UORG." + - name: dt_ultima_transacao + description: "Data da última transação." + - name: cpf + description: "CPF do servidor." + + - name: lista_uorgs + description: > + Tabela bronze com a lista de UORGs. + meta: + tags: + - bronze + columns: + - name: codigo + description: "Código da UORG." + - name: dt_ultima_transacao + description: "Data da última transação." + - name: nome + description: "Nome da UORG." + + - name: terceirizados + description: > + Tabela gold contendo informações de trabalhadores terceirizados oriundos do sistema ComprasGov. + Inclui dados relacionados ao contrato, função, jornada, remuneração e benefícios, com tratamento de campos textuais e numéricos para padronização. + meta: + tags: + - bronze + columns: + - name: id + description: Identificador único do registro do trabalhador terceirizado. + - name: contrato_id + description: Identificador do contrato ao qual o trabalhador está vinculado. + - name: cpf + description: CPF do trabalhador, extraído da string `usuario`. + - name: nome + description: Nome do trabalhador, extraído da string `usuario`. + - name: funcao_id + description: Identificador da função exercida pelo trabalhador. + - name: descricao_complementar + description: Descrição complementar da função ou atividade do trabalhador. + - name: jornada + description: Quantidade de horas de jornada de trabalho semanal do trabalhador (convertido para numérico). + - name: unidade + description: Unidade organizacional onde o trabalhador presta serviço. + - name: salario + description: Valor do salário mensal do trabalhador (convertido para formato numérico padrão). + - name: custo + description: Custo total do trabalhador para a instituição (convertido para formato numérico padrão). + - name: escolaridade_id + description: Identificador do grau de escolaridade do trabalhador. + - name: data_inicio + description: Data de início do contrato do trabalhador, convertida para formato de data. + - name: data_fim + description: Data de término do contrato do trabalhador, convertida para formato de data. + - name: situacao + description: Situação atual do vínculo contratual do trabalhador (ativo, encerrado). + - name: aux_transporte + description: Valor mensal do auxílio transporte recebido pelo trabalhador (convertido para numérico). + - name: vale_alimentacao + description: Valor mensal do vale alimentação recebido pelo trabalhador (convertido para numérico). + + + - name: unidade_organizacional + description: > + Tabela gold que representa a hierarquia das unidades organizacionais extraídas do sistema Siorg. + A hierarquia é construída de forma recursiva, partindo de uma unidade raiz definida manualmente. + A tabela traz dados estruturados sobre as unidades, seus vínculos parentais e o nível de profundidade hierárquica. + meta: + tags: + - bronze + columns: + - name: codigounidade + description: Código da unidade organizacional (sem prefixos de URI). + - name: codigounidadepai + description: Código da unidade organizacional pai, representando a hierarquia entre unidades. + - name: codigoorgaoentidade + description: Código do órgão ou entidade ao qual a unidade pertence. + - name: codigotipounidade + description: Código do tipo da unidade organizacional (departamento, secretaria). + - name: nome + description: Nome completo da unidade organizacional. + - name: sigla + description: Sigla da unidade organizacional. + - name: codigoesfera + description: Código da esfera governamental (federal, estadual, municipal). + - name: codigopoder + description: Código do poder ao qual a unidade está vinculada (Executivo, Judiciário). + - name: codigonaturezajuridica + description: Código da natureza jurídica da unidade. + - name: codigosubnaturezajuridica + description: Código da subnatureza jurídica da unidade. + - name: nivelnormatizacao + description: Nível de normatização da unidade organizacional. + - name: versaoconsulta + description: Número da versão da consulta no momento da extração dos dados. + - name: datafinalversaoconsulta + description: Data de finalização da versão da consulta. + - name: operacao + description: Tipo de operação registrada (inclusão, alteração, exclusão). + - name: codigounidadepaianterior + description: Código da unidade pai anterior (em caso de alteração estrutural). + - name: codigoorgaoentidadeanterior + description: Código do órgão/entidade anterior da unidade (em caso de mudança). + - name: ordem_grandeza + description: Nível hierárquico da unidade dentro da estrutura organizacional (quanto maior, mais profunda). + - name: caminho_unidade + description: Caminho hierárquico concatenado das siglas das unidades até o nível atual. + + From 3b5037d7adfdad0d5478d994d2d93e46852319db Mon Sep 17 00:00:00 2001 From: mat054 Date: Mon, 27 Oct 2025 15:26:31 -0300 Subject: [PATCH 164/317] feat(orcamento bronze): adicionando schema das tabelas bronze de orcamento --- .../ipea/models/orcamento_dbt/bronze/schema.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/orcamento_dbt/bronze/schema.yml diff --git a/airflow_lappis/dags/dbt/ipea/models/orcamento_dbt/bronze/schema.yml b/airflow_lappis/dags/dbt/ipea/models/orcamento_dbt/bronze/schema.yml new file mode 100644 index 00000000..c498db75 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/orcamento_dbt/bronze/schema.yml @@ -0,0 +1,14 @@ +version: 2 + +models: + + # Orçamento DBT + + ## Bronze + - name: visao_orcamentaria_total + description: > + Esta tabela contém a visão consolidada da execução orçamentária total, + apresentando informações agregadas sobre os recursos disponíveis, empenhados, liquidados e pagos. + meta: + tags: + - bronze From 15add6416c9091381e8e6960e2c090ec7f28ca49 Mon Sep 17 00:00:00 2001 From: mat054 Date: Mon, 27 Oct 2025 15:27:14 -0300 Subject: [PATCH 165/317] feat(contratos views): adicionando schema das views de contratos --- .../models/contratos_dbt/views/schema.yml | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/contratos_dbt/views/schema.yml diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/views/schema.yml b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/views/schema.yml new file mode 100644 index 00000000..157ad343 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/views/schema.yml @@ -0,0 +1,30 @@ +version: 2 + +models: + + ## View + - name: identificadores + description: > + Essa tabela contém os identificadores dos contratos, empenhos e faturas. + Consolida os números de empenho encontrados em diferentes fontes (contratos, faturas, empenhos) + e padroniza informações como processo e CNPJ/CPF para facilitar joins entre as tabelas. + Também cria um campo info_complementar que combina unidade gestora, modalidade e número de contrato/licitação. + columns: + - name: contrato_id + description: > + Identificador único do contrato. + - name: categoria + description: > + Categoria do contrato ( Informática, Serviços, Mão de Obra). + - name: processo + description: > + Número do processo administrativo do contrato, formatado para remover caracteres não numéricos. + - name: cnpj_cpf + description: > + CNPJ ou CPF do fornecedor, formatado para remover caracteres especiais como barras, pontos e hífens. + - name: info_complementar + description: > + Informação complementar construída a partir da unidade gestora, código de modalidade e número do contrato ou licitação. + - name: ne + description: > + Número da nota de empenho relacionada ao contrato, combinando informações de contratos, empenhos e faturas. From 678ecf7bc593d72fc09bd93d233364249f7ebe16 Mon Sep 17 00:00:00 2001 From: mat054 Date: Mon, 27 Oct 2025 15:27:56 -0300 Subject: [PATCH 166/317] feat(contratos silver): adicionando schema das tabelas silver de contratos --- .../models/contratos_dbt/silver/schema.yml | 257 ++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/schema.yml diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/schema.yml b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/schema.yml new file mode 100644 index 00000000..60bed0fa --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/schema.yml @@ -0,0 +1,257 @@ +version: 2 + +models: + + ## Silver + - name: contratos_empenhos + description: > + Essa tabela combina as informações de movimentação financeira dos empenhos com os ids de contrato do IPEA. + Realiza uma complexa estratégia de join entre os contratos e empenhos do tesouro, tentando várias abordagens + para relacionar os dados do SIAFI com os contratos do ComprasGov usando: + 1) número de empenho e CNPJ/CPF, 2) número de processo, 3) CNPJ/CPF único, e 4) informação complementar. + meta: + tags: + - silver + columns: + - name: contrato_id + description: > + Identificador único do contrato relacionado ao empenho. + - name: ne_transformed + description: > + Número da nota de empenho padronizado para facilitar joins entre diferentes fontes. + - name: ne_ccor + description: > + Número completo da nota de empenho no SIAFI. + - name: ne_info_complementar + description: > + Informações complementares sobre o empenho no SIAFI. + - name: ne_num_processo + description: > + Número do processo administrativo relacionado ao empenho, formatado para remover caracteres especiais. + - name: ne_ccor_descricao + description: > + Descrição da nota de empenho conforme registrado no SIAFI. + - name: doc_observacao + description: > + Observações registradas no documento do empenho. + - name: natureza_despesa + description: > + Código da natureza da despesa do empenho. + - name: natureza_despesa_descricao + description: > + Descrição da natureza da despesa do empenho. + - name: ne_ccor_favorecido + description: > + CNPJ ou CPF do favorecido do empenho. + - name: ne_ccor_ano_emissao + description: > + Ano de emissão do empenho. + - name: despesas_empenhadas + description: > + Valor total das despesas empenhadas para o contrato. + - name: despesas_liquidadas + description: > + Valor total das despesas liquidadas para o contrato. + - name: despesas_pagas + description: > + Valor total das despesas pagas para o contrato. + + - name: contratos_estagios + description: > + Essa tabela combina as informações de movimentação financeira dos empenhos com os ids de contrato do IPEA mensalmente. + Utiliza uma estratégia de join em cascata, tentando correlacionar os estágios das despesas do SIAFI com os + contratos do ComprasGov através de várias combinações de chaves, incluindo número de empenho, CNPJ/CPF, número + de processo e informações complementares. + meta: + tags: + - silver + columns: + - name: contrato_id + description: > + Identificador único do contrato relacionado aos estágios. + - name: mes_lancamento + description: > + Mês em que o estágio da despesa foi registrado no SIAFI. + - name: valor_empenhado + description: > + Valor empenhado no mês de referência para o contrato. + - name: valor_liquidado + description: > + Valor liquidado no mês de referência para o contrato. + - name: valor_pago + description: > + Valor pago no mês de referência para o contrato. + + - name: estagios_mensal + description: > + Essa tabela discrimina os valores empenhados, liquidados e pagos mensalmente extraídos do SIAFI para cada contrato. + Transforma e agrega os dados da tabela empenhos_tesouro do SIAFI, padronizando formatos e consolidando valores + por número de empenho, mês e CNPJ/CPF. + meta: + tags: + - silver + columns: + - name: ne + description: > + Número da nota de empenho no formato padronizado, extraído das 12 últimas posições do ne_ccor. + - name: mes_lancamento + description: > + Mês de referência do lançamento, convertido para formato de data padrão. + - name: cnpj_cpf + description: > + CNPJ ou CPF do favorecido do empenho. + - name: info_complementar + description: > + Informação complementar extraída do ne_info_complementar. + - name: num_processo + description: > + Número do processo administrativo relacionado ao empenho. + - name: valor_empenhado + description: > + Valor total empenhado no mês e para o empenho específico. + - name: valor_liquidado + description: > + Valor total liquidado no mês e para o empenho específico. + - name: valor_pago + description: > + Valor total pago no mês e para o empenho específico. + + - name: contratos_faturas + description: > + Essa tabela integra informações detalhadas de faturas com dados dos contratos relacionados. + Permite uma visão completa de cada fatura no contexto do seu contrato, incluindo dados como número de contrato, + processo, objeto e fornecedor. Facilita a análise financeira das faturas em conjunto com seus respectivos contratos. + meta: + tags: + - silver + columns: + - name: id + description: > + Identificador único da fatura. + - name: contrato_id + description: > + Identificador do contrato relacionado à fatura. + - name: numero_contrato + description: > + Número do contrato no sistema ComprasGov. + - name: contrato_processo + description: > + Número do processo administrativo relacionado ao contrato. + - name: fornecedor_cnpj_cpf_idgener + description: > + CNPJ, CPF ou identificador genérico do fornecedor do contrato. + - name: objeto_contrato + description: > + Descrição do objeto contratado. + - name: tipolistafatura_id + description: > + Identificador do tipo de lista de fatura. + - name: justificativafatura_id + description: > + Identificador da justificativa da fatura, quando aplicável. + - name: sfadrao_id + description: > + Identificador do padrão de sistema financeiro relacionado. + - name: numero + description: > + Número da fatura emitida pelo fornecedor. + - name: emissao + description: > + Data de emissão da fatura. + - name: prazo + description: > + Data limite para pagamento da fatura. + - name: vencimento + description: > + Data de vencimento da fatura. + - name: valor + description: > + Valor bruto da fatura. + - name: juros + description: > + Valor de juros aplicados à fatura. + - name: multa + description: > + Valor de multa aplicada à fatura. + - name: glosa + description: > + Valor de glosa aplicada à fatura. + - name: valorliquido + description: > + Valor líquido da fatura após subtrações de glosas, multas ou juros. + - name: processo + description: > + Número do processo administrativo específico da fatura, quando diferente do processo do contrato. + - name: protocolo + description: > + Data de protocolo da fatura no sistema. + - name: ateste + description: > + Data em que a fatura foi atestada, confirmando a entrega do produto ou serviço. + - name: repactuacao + description: > + Indica se a fatura está relacionada a uma repactuação contratual. + - name: infcomplementar + description: > + Informações complementares sobre a fatura. + - name: mesref + description: > + Mês de referência da fatura. + - name: anoref + description: > + Ano de referência da fatura. + - name: situacao + description: > + Status atual da fatura (Pago ou Pendente). + - name: chave_nfe + description: > + Chave da Nota Fiscal Eletrônica associada à fatura, quando disponível. + - name: dados_referencia + description: > + Dados de referência adicionais da fatura, geralmente em formato JSON. + - name: dados_item_faturado + description: > + Detalhamento dos itens faturados, geralmente em formato JSON. + - name: dados_empenho + description: > + Informações sobre o empenho relacionado à fatura, em formato JSON. + - name: id_empenho + description: > + Identificador do empenho extraído do campo JSON dados_empenho. + - name: numero_empenho + description: > + Número do empenho relacionado à fatura. + - name: valor_empenho + description: > + Valor do empenho relacionado à fatura. + - name: subelemento + description: > + Código do subelemento de despesa relacionado à fatura. + + - name: cronogramas_faturas_mensal + description: > + Essa tabela discrimina os valores programados e faturados mensalmente pelo ComprasGov para cada contrato. + Combina dados dos cronogramas com as faturas pagas e pendentes, agrupando por contrato e mês de referência, + e calculando o saldo contratual disponível (diferença entre o valor programado e os valores faturados). + meta: + tags: + - silver + columns: + - name: contrato_id + description: > + Identificador único do contrato. + - name: mes_ref + description: > + Mês de referência dos valores programados e faturados. + - name: valor_cronograma + description: > + Valor total programado para o mês de referência conforme cronograma do contrato. + - name: valor_faturas_pagas + description: > + Valor total de faturas com status "Pago" no mês de referência. + - name: valor_faturas_pendentes + description: > + Valor total de faturas com status "Pendente" no mês de referência. + - name: saldo_contratual_disponivel + description: > + Diferença entre o valor programado no cronograma e a soma dos valores faturados (pagos e pendentes). From ea3555eb0dff9c23e881715746a29209c8cd567c Mon Sep 17 00:00:00 2001 From: mat054 Date: Mon, 27 Oct 2025 15:28:22 -0300 Subject: [PATCH 167/317] feat(contratos gold): adiconando schema das tabelas gold de contratos --- .../ipea/models/contratos_dbt/gold/schema.yml | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/schema.yml diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/schema.yml b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/schema.yml new file mode 100644 index 00000000..4315bfd5 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/schema.yml @@ -0,0 +1,139 @@ +version: 2 + +models: + + ## Golds + - name: contratos_resumo + description: > + Essa tabela contém um resumo dos contratos, informações contratuais como valor global e valor pago, + e situação atual, como em vigência ou pendente de baixa. + Facilita a análise gerencial dos contratos com indicadores chave como status de pagamento, tipo de fornecedor, + e identificação de contratos continuados (com vigência superior a dois anos e mais de uma parcela). + meta: + tags: + - gold + columns: + - name: contrato_id + description: > + Identificador único do contrato. + - name: fornecedor_cnpj_cpf + description: > + CNPJ ou CPF do fornecedor contratado, com formatação padronizada. + - name: numero + description: > + Número do contrato no sistema ComprasGov. + - name: categoria + description: > + Categoria do contrato ( Informática, Serviços, Mão de Obra). + - name: modalidade + description: > + Modalidade de licitação utilizada na contratação. + - name: tipo + description: > + Tipo de contrato ( Contrato, Empenho, Termo de Compromisso). + - name: situacao + description: > + Situação atual do contrato (Ativo ou Inativo). + - name: pendente_baixa + description: > + Indica se o contrato está pendente de baixa ('Sim' quando o valor pago for igual ao valor global, 'Não' caso contrário). + - name: fornecedor_nome + description: > + Nome ou razão social do fornecedor contratado. + - name: objeto + description: > + Descrição detalhada do objeto contratado. + - name: valor_global + description: > + Valor total do contrato após eventuais aditivos. + - name: despesas_pagas + description: > + Valor total já pago do contrato conforme registros do SIAFI. + - name: vigencia_inicio + description: > + Data de início da vigência do contrato. + - name: vigencia_fim + description: > + Data de término da vigência do contrato. + - name: num_parcelas + description: > + Número de parcelas para pagamento do contrato. + - name: fornecedor_tipo + description: > + Tipo do fornecedor, categorizado como 'Empresa do Exterior' para IDGENERICO. + - name: Unidade + description: > + Unidade gestora responsável pelo contrato, no formato "código - nome_resumido". + - name: continuado + description: > + Indica se o contrato é de prestação continuada ('Sim' quando a vigência for maior que 730 dias e tiver mais de uma parcela). + + - name: contratos_comparativo_mensal + description: > + Essa tabela contém um comparativo mensal dos contratos, discriminando + os valores empenhados, liquidados e pagos do SIAFI, + programados e faturados do ComprasGov. + Permite a identificação de inconsistências entre os sistemas e o acompanhamento detalhado + da execução financeira mensal de cada contrato. + meta: + tags: + - gold + columns: + - name: contrato_id + description: > + Identificador único do contrato. + - name: mes_ref + description: > + Mês de referência da informação financeira, preenchido para todos os meses entre o início e fim do contrato. + - name: comprasgov_valor_cronograma + description: > + Valor programado para o mês conforme cronograma registrado no ComprasGov. + - name: comprasgov_valor_faturas + description: > + Soma dos valores das faturas (pagas e pendentes) no mês de referência conforme ComprasGov. + - name: comprasgov_saldo_contratual_disponivel + description: > + Diferença entre o valor programado e o valor faturado no mês, indicando o saldo disponível. + - name: siafi_valor_empenhado + description: > + Valor empenhado no mês de referência conforme registros do SIAFI. + - name: siafi_valor_liquidado + description: > + Valor liquidado no mês de referência conforme registros do SIAFI. + - name: siafi_valor_pago + description: > + Valor pago no mês de referência conforme registros do SIAFI. + + - name: contratos_somatorio + description: > + Essa tabela contém somatórios dos valores de cronograma, fatura, empenho, liquidação e pagamento para cada contrato. + Facilita análises agregadas sobre a execução financeira total de cada contrato, com indicadores importantes + como o orçamento pendente de execução. + meta: + tags: + - gold + columns: + - name: contrato_id + description: > + Identificador único do contrato. + - name: total_cronograma + description: > + Soma de todos os valores programados no cronograma do contrato. + - name: total_faturas + description: > + Soma de todos os valores faturados (pagos e pendentes) para o contrato. + - name: total_saldo_disponivel + description: > + Diferença total entre valores programados e faturados, indicando o saldo contratual disponível. + - name: orcamento_a_executar + description: > + Soma dos valores programados em meses onde ainda não houve faturamento, indicando o orçamento pendente de execução. + - name: total_empenhado + description: > + Soma de todos os valores empenhados para o contrato segundo o SIAFI. + - name: total_liquidado + description: > + Soma de todos os valores liquidados para o contrato segundo o SIAFI. + - name: total_pago + description: > + Soma de todos os valores pagos para o contrato segundo o SIAFI. From 767e13200f6e75d49da5a71dd431bd57d138fa91 Mon Sep 17 00:00:00 2001 From: mat054 Date: Mon, 27 Oct 2025 15:28:49 -0300 Subject: [PATCH 168/317] feat(contratos bronze): adicionando schemas das tabelas bronze de contratos --- .../models/contratos_dbt/bronze/schema.yml | 465 ++++++++++++++++++ 1 file changed, 465 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/schema.yml diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/schema.yml b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/schema.yml new file mode 100644 index 00000000..d251b2a6 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/schema.yml @@ -0,0 +1,465 @@ +version: 2 + +models: + + # Contratos DBT + + ## Bronze + - name: contratos + description: > + Tabela com informações sobre contratos, incluindo detalhes como o valor do contrato, a data de início e término, e o status do contrato. + Esta tabela é fundamental para entender a execução e o cumprimento dos contratos firmados. + A tabela é atualizada diariamente e contém dados de contratos firmados pelo IPEA. + A tabela realiza validações e limpezas dos dados extraídos do ComprasGov, incluindo formatação adequada de valores numéricos, + remoção de caracteres especiais em CPF/CNPJ e normalização de datas. + meta: + tags: + - bronze + columns: + - name: id + description: > + Identificador único do contrato, utilizado para referenciar o contrato em outras tabelas e análises. + - name: receita_despesa + description: > + Indica se o contrato é de receita ou despesa. + - name: numero + description: > + Número do contrato no sistema ComprasGov. + - name: fornecedor_tipo + description: > + Tipo do fornecedor (PF, PJ, IDGENERICO para empresas do exterior). + - name: fornecedor_nome + description: > + Nome do fornecedor contratado. + - name: tipo + description: > + Tipo de contrato ( Contrato, Empenho, Termo de Compromisso, etc.). + - name: situacao + description: > + Situação atual do contrato ( Ativo, Inativo). + - name: categoria + description: > + Categoria do contrato ( Informática, Serviços, Mão de Obra, Serviços de Engenharia, etc.). + - name: objeto + description: > + Descrição do objeto contratado. + - name: codigo_modalidade + description: > + Código que identifica a modalidade do contrato. + - name: modalidade + description: > + Modalidade do contrato ( Pregão, Dispensa, Inexigibilidade, etc.). + - name: num_parcelas + description: > + Número de parcelas para pagamento do contrato. + - name: valor_inicial + description: > + Valor inicial do contrato, formatado como numérico. + - name: valor_global + description: > + Valor total do contrato após eventuais aditivos, formatado como numérico. + - name: valor_parcela + description: > + Valor de cada parcela do contrato, formatado como numérico. + - name: valor_acumulado + description: > + Valor acumulado já realizado do contrato, formatado como numérico. + - name: fornecedor_cnpj_cpf_idgener + description: > + CNPJ, CPF ou identificador genérico do fornecedor, com formatação padronizada para remoção de caracteres especiais. + - name: processo + description: > + Número do processo administrativo relacionado ao contrato, formatado para remover caracteres especiais. + - name: data_assinatura + description: > + Data em que o contrato foi assinado, validada e convertida para formato de data padrão. + - name: data_publicacao + description: > + Data de publicação do contrato no Diário Oficial, validada e convertida para formato de data padrão. + - name: vigencia_inicio + description: > + Data de início da vigência do contrato, validada e convertida para formato de data padrão. + - name: vigencia_fim + description: > + Data de término da vigência do contrato, validada e convertida para formato de data padrão. + data_tests: + - row_count_match: + source_table: compras_gov.contratos + target_table: contratos.contratos + - verificacao_tipagem: + nome_tabela: 'contratos.contratos' + nome_coluna: 'num_parcelas' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'contratos.contratos' + nome_coluna: 'valor_inicial' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.contratos' + nome_coluna: 'valor_global' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.contratos' + nome_coluna: 'valor_parcela' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.contratos' + nome_coluna: 'valor_acumulado' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.contratos' + nome_coluna: 'data_assinatura' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'contratos.contratos' + nome_coluna: 'data_publicacao' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'contratos.contratos' + nome_coluna: 'data_proposta_comercial' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'contratos.contratos' + nome_coluna: 'vigencia_inicio' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'contratos.contratos' + nome_coluna: 'vigencia_fim' + tipo_esperado: 'date' + + - name: cronogramas + description: > + Essa tabela contém informações sobre as despesas mensais programadas para cada contrato. + Os dados são extraídos da tabela cronograma do ComprasGov, com valores formatados adequadamente. + Apresenta o planejamento financeiro do contrato distribuído em parcelas mensais. + meta: + tags: + - bronze + columns: + - name: id + description: > + Identificador único do cronograma. + - name: contrato_id + description: > + Identificador do contrato relacionado ao cronograma. + - name: tipo + description: > + Tipo de cada item do cronograma. + - name: mesref + description: > + Mês de referência do cronograma. + - name: anoref + description: > + Ano de referência do cronograma. + - name: valor + description: > + Valor programado para o mês e ano de referência, formatado como numérico. + - name: vencimento + description: > + Data de vencimento da parcela do cronograma. + data_tests: + - verificacao_tipagem: + nome_tabela: 'contratos.cronogramas' + nome_coluna: 'id' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'contratos.cronogramas' + nome_coluna: 'contrato_id' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'contratos.cronogramas' + nome_coluna: 'mesref' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'contratos.cronogramas' + nome_coluna: 'anoref' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'contratos.cronogramas' + nome_coluna: 'valor' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.cronogramas' + nome_coluna: 'vencimento' + tipo_esperado: 'date' + + - name: faturas + description: > + Essa tabela contém informações sobre as faturas emitidas mensalmente de cada contrato. + Os dados são extraídos da tabela faturas do ComprasGov com formatação adequada de valores numéricos e datas. + A tabela inclui um processamento adicional para extrair informações dos empenhos a partir do campo JSON dados_empenho. + meta: + tags: + - bronze + columns: + - name: id + description: > + Identificador único da fatura. + - name: contrato_id + description: > + Identificador do contrato relacionado à fatura. + - name: numero + description: > + Número da fatura emitida pelo fornecedor. + - name: emissao + description: > + Data de emissão da fatura pelo fornecedor. + - name: prazo + description: > + Data limite para pagamento da fatura. + - name: vencimento + description: > + Data de vencimento da fatura. + - name: valor + description: > + Valor bruto da fatura, formatado como numérico. + - name: juros + description: > + Valor de juros aplicados à fatura, formatado como numérico. + - name: multa + description: > + Valor de multa aplicada à fatura, formatado como numérico. + - name: glosa + description: > + Valor de glosa aplicada à fatura, formatado como numérico. + - name: valorliquido + description: > + Valor líquido da fatura após subtrações de glosas, multas ou juros, formatado como numérico. + - name: situacao + description: > + Status atual da fatura (Pago ou Pendente). + - name: mesref + description: > + Mês de referência da fatura. + - name: anoref + description: > + Ano de referência da fatura. + - name: id_empenho + description: > + Identificador do empenho extraído do campo JSON dados_empenho. + - name: numero_empenho + description: > + Número do empenho relacionado à fatura, extraído do campo JSON dados_empenho e convertido para maiúsculas. + - name: valor_empenho + description: > + Valor do empenho extraído do campo JSON dados_empenho. + data_tests: + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'id' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'contrato_id' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'emissao' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'prazo' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'vencimento' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'valor' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'juros' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'multa' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'glosa' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'valorliquido' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'protocolo' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'ateste' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'mesref' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'contratos.faturas' + nome_coluna: 'anoref' + tipo_esperado: 'integer' + + - name: empenhos + description: > + Essa tabela contém informações sobre os empenhos de cada contrato extraídos do ComprasGov. + Os dados são formatados adequadamente para garantir consistência nos tipos numéricos e de data. + Registra os compromissos de pagamento assumidos pela administração para cada contrato. + meta: + tags: + - bronze + columns: + - name: id + description: > + Identificador único do empenho no ComprasGov. + - name: contrato_id + description: > + Identificador do contrato relacionado ao empenho. + - name: nota_empenho + description: > + Número da nota de empenho registrada no sistema. + - name: credor + description: > + Nome ou razão social do credor do empenho. + - name: credor_obj_cnpj_cpf_idgener + description: > + CNPJ, CPF ou identificador genérico do credor do empenho. + - name: empenhado + description: > + Valor total empenhado, formatado como numérico. + - name: aliquidar + description: > + Valor a liquidar do empenho, formatado como numérico. + - name: liquidado + description: > + Valor já liquidado do empenho, formatado como numérico. + - name: pago + description: > + Valor já pago do empenho, formatado como numérico. + - name: rpinscrito + description: > + Valor inscrito em restos a pagar, formatado como numérico. + - name: rpaliquidar + description: > + Valor inscrito em restos a pagar a liquidar, formatado como numérico. + - name: rpliquidado + description: > + Valor inscrito em restos a pagar já liquidado, formatado como numérico. + - name: rppago + description: > + Valor inscrito em restos a pagar já pago, formatado como numérico. + - name: data_emissao + description: > + Data de emissão do empenho, validada e convertida para formato de data padrão. + data_tests: + - row_count_match: + source_table: compras_gov.empenhos + target_table: contratos.empenhos + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos' + nome_coluna: 'empenhado' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos' + nome_coluna: 'aliquidar' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos' + nome_coluna: 'liquidado' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos' + nome_coluna: 'pago' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos' + nome_coluna: 'rpinscrito' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos' + nome_coluna: 'rpaliquidar' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos' + nome_coluna: 'rpliquidado' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos' + nome_coluna: 'rppago' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos' + nome_coluna: 'data_emissao' + tipo_esperado: 'date' + + - name: empenhos_tesouro + description: > + Essa tabela contém informações sobre movimentação financeira dos empenhos extraídos do SIAFI. + Contém dados de empenhos do Tesouro Nacional com formatação de valores financeiros através da macro parse_financial_value. + Apresenta valores para as diferentes etapas da execução orçamentária (empenho, liquidação, pagamento). + meta: + tags: + - bronze + columns: + - name: emissao_mes + description: > + Mês de emissão do empenho. + - name: emissao_dia + description: > + Data específica de emissão do empenho. + - name: ne_ccor + description: > + Número completo da nota de empenho no SIAFI. + - name: ne_num_processo + description: > + Número do processo relacionado ao empenho, com formatação para remover caracteres especiais. + - name: ne_info_complementar + description: > + Informações complementares sobre o empenho. + - name: ne_ccor_favorecido + description: > + CNPJ ou CPF do favorecido, formatado em caixa alta. + - name: ne_ccor_ano_emissao + description: > + Ano de emissão do empenho, filtrado para incluir apenas empenhos a partir do ano 2000. + - name: despesas_empenhadas + description: > + Valor das despesas empenhadas, formatado como numérico. + - name: despesas_liquidadas + description: > + Valor das despesas liquidadas, formatado como numérico. + - name: despesas_pagas + description: > + Valor das despesas pagas, formatado como numérico. + - name: restos_a_pagar_inscritos + description: > + Valor dos restos a pagar inscritos, formatado como numérico. + - name: restos_a_pagar_pagos + description: > + Valor dos restos a pagar pagos, formatado como numérico. + data_tests: + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos_tesouro' + nome_coluna: 'ne_ccor_ano_emissao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos_tesouro' + nome_coluna: 'despesas_empenhadas' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos_tesouro' + nome_coluna: 'despesas_liquidadas' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos_tesouro' + nome_coluna: 'despesas_pagas' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos_tesouro' + nome_coluna: 'restos_a_pagar_inscritos' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'contratos.empenhos_tesouro' + nome_coluna: 'restos_a_pagar_pagos' + tipo_esperado: 'numeric' From 5acf698fea719821f95222c0c6e266f8443bcf36 Mon Sep 17 00:00:00 2001 From: mat054 Date: Mon, 27 Oct 2025 15:29:15 -0300 Subject: [PATCH 169/317] feat(macros): adicionando schema das macros na pasta das macros --- .../dags/dbt/ipea/macros/schema.yml | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/macros/schema.yml diff --git a/airflow_lappis/dags/dbt/ipea/macros/schema.yml b/airflow_lappis/dags/dbt/ipea/macros/schema.yml new file mode 100644 index 00000000..694f3b23 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/macros/schema.yml @@ -0,0 +1,24 @@ + +version: 2 + +macros: + - name: create_udfs + description: > + Função que cria as UDFs necessárias para o funcionamento do projeto. + Essa função deve ser chamada no início de cada run para garantir que todas as UDFs estejam disponíveis. + + - name: generate_schema_name + description: > + Função que gera o nome do schema a ser utilizado no projeto. + A função dentro desta macro é built-in do dbt. + + ## UDFS + - name: create_f_parse_dates + description: > + Função que cria a UDF f_parse_dates, que é utilizada para converter texto no formato MÊS(texto)/ANO(numero) em datas. + arguments: + - name: in_text + type: text + description: > + Texto a ser convertido em data. + O texto deve estar no formato MÊS(texto)/ANO(numero). Ex.: FEV/2024 -> 2024-02-01 \ No newline at end of file From bcba8c9dad52a26448b600aa64ad1939dde42f3f Mon Sep 17 00:00:00 2001 From: mat054 Date: Mon, 27 Oct 2025 15:33:10 -0300 Subject: [PATCH 170/317] fix(schema oficial): removendo schema de todas as tabelas juntas --- .../dags/dbt/ipea/models/schema.yml | 2966 ----------------- 1 file changed, 2966 deletions(-) delete mode 100644 airflow_lappis/dags/dbt/ipea/models/schema.yml diff --git a/airflow_lappis/dags/dbt/ipea/models/schema.yml b/airflow_lappis/dags/dbt/ipea/models/schema.yml deleted file mode 100644 index 526f345f..00000000 --- a/airflow_lappis/dags/dbt/ipea/models/schema.yml +++ /dev/null @@ -1,2966 +0,0 @@ -version: 2 - -models: - - # Contratos DBT - - ## Bronze - - name: contratos - description: > - Tabela com informações sobre contratos, incluindo detalhes como o valor do contrato, a data de início e término, e o status do contrato. - Esta tabela é fundamental para entender a execução e o cumprimento dos contratos firmados. - A tabela é atualizada diariamente e contém dados de contratos firmados pelo IPEA. - A tabela realiza validações e limpezas dos dados extraídos do ComprasGov, incluindo formatação adequada de valores numéricos, - remoção de caracteres especiais em CPF/CNPJ e normalização de datas. - meta: - tags: - - bronze - columns: - - name: id - description: > - Identificador único do contrato, utilizado para referenciar o contrato em outras tabelas e análises. - - name: receita_despesa - description: > - Indica se o contrato é de receita ou despesa. - - name: numero - description: > - Número do contrato no sistema ComprasGov. - - name: fornecedor_tipo - description: > - Tipo do fornecedor (PF, PJ, IDGENERICO para empresas do exterior). - - name: fornecedor_nome - description: > - Nome do fornecedor contratado. - - name: tipo - description: > - Tipo de contrato ( Contrato, Empenho, Termo de Compromisso, etc.). - - name: situacao - description: > - Situação atual do contrato ( Ativo, Inativo). - - name: categoria - description: > - Categoria do contrato ( Informática, Serviços, Mão de Obra, Serviços de Engenharia, etc.). - - name: objeto - description: > - Descrição do objeto contratado. - - name: codigo_modalidade - description: > - Código que identifica a modalidade do contrato. - - name: modalidade - description: > - Modalidade do contrato ( Pregão, Dispensa, Inexigibilidade, etc.). - - name: num_parcelas - description: > - Número de parcelas para pagamento do contrato. - - name: valor_inicial - description: > - Valor inicial do contrato, formatado como numérico. - - name: valor_global - description: > - Valor total do contrato após eventuais aditivos, formatado como numérico. - - name: valor_parcela - description: > - Valor de cada parcela do contrato, formatado como numérico. - - name: valor_acumulado - description: > - Valor acumulado já realizado do contrato, formatado como numérico. - - name: fornecedor_cnpj_cpf_idgener - description: > - CNPJ, CPF ou identificador genérico do fornecedor, com formatação padronizada para remoção de caracteres especiais. - - name: processo - description: > - Número do processo administrativo relacionado ao contrato, formatado para remover caracteres especiais. - - name: data_assinatura - description: > - Data em que o contrato foi assinado, validada e convertida para formato de data padrão. - - name: data_publicacao - description: > - Data de publicação do contrato no Diário Oficial, validada e convertida para formato de data padrão. - - name: vigencia_inicio - description: > - Data de início da vigência do contrato, validada e convertida para formato de data padrão. - - name: vigencia_fim - description: > - Data de término da vigência do contrato, validada e convertida para formato de data padrão. - data_tests: - - row_count_match: - source_table: compras_gov.contratos - target_table: contratos.contratos - - verificacao_tipagem: - nome_tabela: 'contratos.contratos' - nome_coluna: 'num_parcelas' - tipo_esperado: 'integer' - - verificacao_tipagem: - nome_tabela: 'contratos.contratos' - nome_coluna: 'valor_inicial' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.contratos' - nome_coluna: 'valor_global' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.contratos' - nome_coluna: 'valor_parcela' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.contratos' - nome_coluna: 'valor_acumulado' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.contratos' - nome_coluna: 'data_assinatura' - tipo_esperado: 'date' - - verificacao_tipagem: - nome_tabela: 'contratos.contratos' - nome_coluna: 'data_publicacao' - tipo_esperado: 'date' - - verificacao_tipagem: - nome_tabela: 'contratos.contratos' - nome_coluna: 'data_proposta_comercial' - tipo_esperado: 'date' - - verificacao_tipagem: - nome_tabela: 'contratos.contratos' - nome_coluna: 'vigencia_inicio' - tipo_esperado: 'date' - - verificacao_tipagem: - nome_tabela: 'contratos.contratos' - nome_coluna: 'vigencia_fim' - tipo_esperado: 'date' - - - name: cronogramas - description: > - Essa tabela contém informações sobre as despesas mensais programadas para cada contrato. - Os dados são extraídos da tabela cronograma do ComprasGov, com valores formatados adequadamente. - Apresenta o planejamento financeiro do contrato distribuído em parcelas mensais. - meta: - tags: - - bronze - columns: - - name: id - description: > - Identificador único do cronograma. - - name: contrato_id - description: > - Identificador do contrato relacionado ao cronograma. - - name: tipo - description: > - Tipo de cada item do cronograma. - - name: mesref - description: > - Mês de referência do cronograma. - - name: anoref - description: > - Ano de referência do cronograma. - - name: valor - description: > - Valor programado para o mês e ano de referência, formatado como numérico. - - name: vencimento - description: > - Data de vencimento da parcela do cronograma. - data_tests: - - verificacao_tipagem: - nome_tabela: 'contratos.cronogramas' - nome_coluna: 'id' - tipo_esperado: 'integer' - - verificacao_tipagem: - nome_tabela: 'contratos.cronogramas' - nome_coluna: 'contrato_id' - tipo_esperado: 'integer' - - verificacao_tipagem: - nome_tabela: 'contratos.cronogramas' - nome_coluna: 'mesref' - tipo_esperado: 'integer' - - verificacao_tipagem: - nome_tabela: 'contratos.cronogramas' - nome_coluna: 'anoref' - tipo_esperado: 'integer' - - verificacao_tipagem: - nome_tabela: 'contratos.cronogramas' - nome_coluna: 'valor' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.cronogramas' - nome_coluna: 'vencimento' - tipo_esperado: 'date' - - - name: faturas - description: > - Essa tabela contém informações sobre as faturas emitidas mensalmente de cada contrato. - Os dados são extraídos da tabela faturas do ComprasGov com formatação adequada de valores numéricos e datas. - A tabela inclui um processamento adicional para extrair informações dos empenhos a partir do campo JSON dados_empenho. - meta: - tags: - - bronze - columns: - - name: id - description: > - Identificador único da fatura. - - name: contrato_id - description: > - Identificador do contrato relacionado à fatura. - - name: numero - description: > - Número da fatura emitida pelo fornecedor. - - name: emissao - description: > - Data de emissão da fatura pelo fornecedor. - - name: prazo - description: > - Data limite para pagamento da fatura. - - name: vencimento - description: > - Data de vencimento da fatura. - - name: valor - description: > - Valor bruto da fatura, formatado como numérico. - - name: juros - description: > - Valor de juros aplicados à fatura, formatado como numérico. - - name: multa - description: > - Valor de multa aplicada à fatura, formatado como numérico. - - name: glosa - description: > - Valor de glosa aplicada à fatura, formatado como numérico. - - name: valorliquido - description: > - Valor líquido da fatura após subtrações de glosas, multas ou juros, formatado como numérico. - - name: situacao - description: > - Status atual da fatura (Pago ou Pendente). - - name: mesref - description: > - Mês de referência da fatura. - - name: anoref - description: > - Ano de referência da fatura. - - name: id_empenho - description: > - Identificador do empenho extraído do campo JSON dados_empenho. - - name: numero_empenho - description: > - Número do empenho relacionado à fatura, extraído do campo JSON dados_empenho e convertido para maiúsculas. - - name: valor_empenho - description: > - Valor do empenho extraído do campo JSON dados_empenho. - data_tests: - - verificacao_tipagem: - nome_tabela: 'contratos.faturas' - nome_coluna: 'id' - tipo_esperado: 'integer' - - verificacao_tipagem: - nome_tabela: 'contratos.faturas' - nome_coluna: 'contrato_id' - tipo_esperado: 'integer' - - verificacao_tipagem: - nome_tabela: 'contratos.faturas' - nome_coluna: 'emissao' - tipo_esperado: 'date' - - verificacao_tipagem: - nome_tabela: 'contratos.faturas' - nome_coluna: 'prazo' - tipo_esperado: 'date' - - verificacao_tipagem: - nome_tabela: 'contratos.faturas' - nome_coluna: 'vencimento' - tipo_esperado: 'date' - - verificacao_tipagem: - nome_tabela: 'contratos.faturas' - nome_coluna: 'valor' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.faturas' - nome_coluna: 'juros' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.faturas' - nome_coluna: 'multa' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.faturas' - nome_coluna: 'glosa' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.faturas' - nome_coluna: 'valorliquido' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.faturas' - nome_coluna: 'protocolo' - tipo_esperado: 'date' - - verificacao_tipagem: - nome_tabela: 'contratos.faturas' - nome_coluna: 'ateste' - tipo_esperado: 'date' - - verificacao_tipagem: - nome_tabela: 'contratos.faturas' - nome_coluna: 'mesref' - tipo_esperado: 'integer' - - verificacao_tipagem: - nome_tabela: 'contratos.faturas' - nome_coluna: 'anoref' - tipo_esperado: 'integer' - - - name: empenhos - description: > - Essa tabela contém informações sobre os empenhos de cada contrato extraídos do ComprasGov. - Os dados são formatados adequadamente para garantir consistência nos tipos numéricos e de data. - Registra os compromissos de pagamento assumidos pela administração para cada contrato. - meta: - tags: - - bronze - columns: - - name: id - description: > - Identificador único do empenho no ComprasGov. - - name: contrato_id - description: > - Identificador do contrato relacionado ao empenho. - - name: nota_empenho - description: > - Número da nota de empenho registrada no sistema. - - name: credor - description: > - Nome ou razão social do credor do empenho. - - name: credor_obj_cnpj_cpf_idgener - description: > - CNPJ, CPF ou identificador genérico do credor do empenho. - - name: empenhado - description: > - Valor total empenhado, formatado como numérico. - - name: aliquidar - description: > - Valor a liquidar do empenho, formatado como numérico. - - name: liquidado - description: > - Valor já liquidado do empenho, formatado como numérico. - - name: pago - description: > - Valor já pago do empenho, formatado como numérico. - - name: rpinscrito - description: > - Valor inscrito em restos a pagar, formatado como numérico. - - name: rpaliquidar - description: > - Valor inscrito em restos a pagar a liquidar, formatado como numérico. - - name: rpliquidado - description: > - Valor inscrito em restos a pagar já liquidado, formatado como numérico. - - name: rppago - description: > - Valor inscrito em restos a pagar já pago, formatado como numérico. - - name: data_emissao - description: > - Data de emissão do empenho, validada e convertida para formato de data padrão. - data_tests: - - row_count_match: - source_table: compras_gov.empenhos - target_table: contratos.empenhos - - verificacao_tipagem: - nome_tabela: 'contratos.empenhos' - nome_coluna: 'empenhado' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.empenhos' - nome_coluna: 'aliquidar' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.empenhos' - nome_coluna: 'liquidado' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.empenhos' - nome_coluna: 'pago' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.empenhos' - nome_coluna: 'rpinscrito' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.empenhos' - nome_coluna: 'rpaliquidar' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.empenhos' - nome_coluna: 'rpliquidado' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.empenhos' - nome_coluna: 'rppago' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.empenhos' - nome_coluna: 'data_emissao' - tipo_esperado: 'date' - - - name: empenhos_tesouro - description: > - Essa tabela contém informações sobre movimentação financeira dos empenhos extraídos do SIAFI. - Contém dados de empenhos do Tesouro Nacional com formatação de valores financeiros através da macro parse_financial_value. - Apresenta valores para as diferentes etapas da execução orçamentária (empenho, liquidação, pagamento). - meta: - tags: - - bronze - columns: - - name: emissao_mes - description: > - Mês de emissão do empenho. - - name: emissao_dia - description: > - Data específica de emissão do empenho. - - name: ne_ccor - description: > - Número completo da nota de empenho no SIAFI. - - name: ne_num_processo - description: > - Número do processo relacionado ao empenho, com formatação para remover caracteres especiais. - - name: ne_info_complementar - description: > - Informações complementares sobre o empenho. - - name: ne_ccor_favorecido - description: > - CNPJ ou CPF do favorecido, formatado em caixa alta. - - name: ne_ccor_ano_emissao - description: > - Ano de emissão do empenho, filtrado para incluir apenas empenhos a partir do ano 2000. - - name: despesas_empenhadas - description: > - Valor das despesas empenhadas, formatado como numérico. - - name: despesas_liquidadas - description: > - Valor das despesas liquidadas, formatado como numérico. - - name: despesas_pagas - description: > - Valor das despesas pagas, formatado como numérico. - - name: restos_a_pagar_inscritos - description: > - Valor dos restos a pagar inscritos, formatado como numérico. - - name: restos_a_pagar_pagos - description: > - Valor dos restos a pagar pagos, formatado como numérico. - data_tests: - - verificacao_tipagem: - nome_tabela: 'contratos.empenhos_tesouro' - nome_coluna: 'ne_ccor_ano_emissao' - tipo_esperado: 'integer' - - verificacao_tipagem: - nome_tabela: 'contratos.empenhos_tesouro' - nome_coluna: 'despesas_empenhadas' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.empenhos_tesouro' - nome_coluna: 'despesas_liquidadas' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.empenhos_tesouro' - nome_coluna: 'despesas_pagas' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.empenhos_tesouro' - nome_coluna: 'restos_a_pagar_inscritos' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.empenhos_tesouro' - nome_coluna: 'restos_a_pagar_pagos' - tipo_esperado: 'numeric' - - - name: estagios - description: > - Essa tabela contém informações sobre os eventos de cada empenho discriminados por mês. - Extrai dados da tabela estagios_tesouro do SIAFI com formatação e padronização para facilitar análises. - Trata especificamente valores monetários com parênteses, convertendo-os para números negativos. - Apresenta os diferentes estágios da despesa (empenho, liquidação, pagamento) ao longo do tempo. - meta: - tags: - - bronze - columns: - - name: ne_ccor - description: > - Número completo da nota de empenho no SIAFI. - - name: ne_informacao_complementar - description: > - Informações complementares sobre o empenho. - - name: doc_observacao - description: > - Observações adicionais registradas no documento. - - name: ne_ccor_favorecido - description: > - CNPJ ou CPF do favorecido do empenho. - - name: mes_lancamento - description: > - Mês em que o lançamento foi registrado no SIAFI. - - name: ne_num_processo - description: > - Número do processo relacionado ao empenho, formatado para remover caracteres especiais. - - name: natureza_despesa - description: > - Código da natureza de despesa, convertido para integer quando possível. - - name: natureza_despesa_detalhada - description: > - Código detalhado da natureza de despesa, convertido para integer quando possível. - - name: ano_lancamento - description: > - Ano em que o lançamento foi registrado no SIAFI, convertido para integer quando possível. - - name: ne_ccor_ano_emissao - description: > - Ano de emissão do empenho, convertido para integer quando possível. - - name: despesas_empenhadas_controle_empenho_saldo_moeda_origem - description: > - Saldo das despesas empenhadas em moeda de origem, formatado como numérico. - - name: despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem - description: > - Movimento líquido das despesas empenhadas em moeda de origem, formatado como numérico. - - name: despesas_liquidadas_controle_empenho_saldo_moeda_origem - description: > - Saldo das despesas liquidadas em moeda de origem, formatado como numérico. - - name: despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem - description: > - Movimento líquido das despesas liquidadas em moeda de origem, formatado como numérico. - - name: despesas_pagas_controle_empenho_saldo_moeda_origem - description: > - Saldo das despesas pagas em moeda de origem, formatado como numérico. - - name: despesas_pagas_controle_empenho_movim_liquido_moeda_origem - description: > - Movimento líquido das despesas pagas em moeda de origem, formatado como numérico. - data_tests: - - verificacao_tipagem: - nome_tabela: 'contratos.estagios' - nome_coluna: 'natureza_despesa' - tipo_esperado: 'integer' - - verificacao_tipagem: - nome_tabela: 'contratos.estagios' - nome_coluna: 'natureza_despesa_detalhada' - tipo_esperado: 'integer' - - verificacao_tipagem: - nome_tabela: 'contratos.estagios' - nome_coluna: 'ano_lancamento' - tipo_esperado: 'integer' - - verificacao_tipagem: - nome_tabela: 'contratos.estagios' - nome_coluna: 'ne_ccor_ano_emissao' - tipo_esperado: 'integer' - - verificacao_tipagem: - nome_tabela: 'contratos.estagios' - nome_coluna: 'despesas_empenhadas_controle_empenho_saldo_moeda_origem' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.estagios' - nome_coluna: 'despesas_empenhadas_controle_empenho_movim_liquido_moeda_origem' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.estagios' - nome_coluna: 'despesas_liquidadas_controle_empenho_saldo_moeda_origem' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.estagios' - nome_coluna: 'despesas_liquidadas_controle_empenho_movim_liquido_moeda_origem' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.estagios' - nome_coluna: 'despesas_pagas_controle_empenho_saldo_moeda_origem' - tipo_esperado: 'numeric' - - verificacao_tipagem: - nome_tabela: 'contratos.estagios' - nome_coluna: 'despesas_pagas_controle_empenho_movim_liquido_moeda_origem' - tipo_esperado: 'numeric' - - ## Silver - - name: contratos_empenhos - description: > - Essa tabela combina as informações de movimentação financeira dos empenhos com os ids de contrato do IPEA. - Realiza uma complexa estratégia de join entre os contratos e empenhos do tesouro, tentando várias abordagens - para relacionar os dados do SIAFI com os contratos do ComprasGov usando: - 1) número de empenho e CNPJ/CPF, 2) número de processo, 3) CNPJ/CPF único, e 4) informação complementar. - meta: - tags: - - silver - columns: - - name: contrato_id - description: > - Identificador único do contrato relacionado ao empenho. - - name: ne_transformed - description: > - Número da nota de empenho padronizado para facilitar joins entre diferentes fontes. - - name: ne_ccor - description: > - Número completo da nota de empenho no SIAFI. - - name: ne_info_complementar - description: > - Informações complementares sobre o empenho no SIAFI. - - name: ne_num_processo - description: > - Número do processo administrativo relacionado ao empenho, formatado para remover caracteres especiais. - - name: ne_ccor_descricao - description: > - Descrição da nota de empenho conforme registrado no SIAFI. - - name: doc_observacao - description: > - Observações registradas no documento do empenho. - - name: natureza_despesa - description: > - Código da natureza da despesa do empenho. - - name: natureza_despesa_descricao - description: > - Descrição da natureza da despesa do empenho. - - name: ne_ccor_favorecido - description: > - CNPJ ou CPF do favorecido do empenho. - - name: ne_ccor_ano_emissao - description: > - Ano de emissão do empenho. - - name: despesas_empenhadas - description: > - Valor total das despesas empenhadas para o contrato. - - name: despesas_liquidadas - description: > - Valor total das despesas liquidadas para o contrato. - - name: despesas_pagas - description: > - Valor total das despesas pagas para o contrato. - - - name: contratos_estagios - description: > - Essa tabela combina as informações de movimentação financeira dos empenhos com os ids de contrato do IPEA mensalmente. - Utiliza uma estratégia de join em cascata, tentando correlacionar os estágios das despesas do SIAFI com os - contratos do ComprasGov através de várias combinações de chaves, incluindo número de empenho, CNPJ/CPF, número - de processo e informações complementares. - meta: - tags: - - silver - columns: - - name: contrato_id - description: > - Identificador único do contrato relacionado aos estágios. - - name: mes_lancamento - description: > - Mês em que o estágio da despesa foi registrado no SIAFI. - - name: valor_empenhado - description: > - Valor empenhado no mês de referência para o contrato. - - name: valor_liquidado - description: > - Valor liquidado no mês de referência para o contrato. - - name: valor_pago - description: > - Valor pago no mês de referência para o contrato. - - - name: estagios_mensal - description: > - Essa tabela discrimina os valores empenhados, liquidados e pagos mensalmente extraídos do SIAFI para cada contrato. - Transforma e agrega os dados da tabela empenhos_tesouro do SIAFI, padronizando formatos e consolidando valores - por número de empenho, mês e CNPJ/CPF. - meta: - tags: - - silver - columns: - - name: ne - description: > - Número da nota de empenho no formato padronizado, extraído das 12 últimas posições do ne_ccor. - - name: mes_lancamento - description: > - Mês de referência do lançamento, convertido para formato de data padrão. - - name: cnpj_cpf - description: > - CNPJ ou CPF do favorecido do empenho. - - name: info_complementar - description: > - Informação complementar extraída do ne_info_complementar. - - name: num_processo - description: > - Número do processo administrativo relacionado ao empenho. - - name: valor_empenhado - description: > - Valor total empenhado no mês e para o empenho específico. - - name: valor_liquidado - description: > - Valor total liquidado no mês e para o empenho específico. - - name: valor_pago - description: > - Valor total pago no mês e para o empenho específico. - - - name: contratos_faturas - description: > - Essa tabela integra informações detalhadas de faturas com dados dos contratos relacionados. - Permite uma visão completa de cada fatura no contexto do seu contrato, incluindo dados como número de contrato, - processo, objeto e fornecedor. Facilita a análise financeira das faturas em conjunto com seus respectivos contratos. - meta: - tags: - - silver - columns: - - name: id - description: > - Identificador único da fatura. - - name: contrato_id - description: > - Identificador do contrato relacionado à fatura. - - name: numero_contrato - description: > - Número do contrato no sistema ComprasGov. - - name: contrato_processo - description: > - Número do processo administrativo relacionado ao contrato. - - name: fornecedor_cnpj_cpf_idgener - description: > - CNPJ, CPF ou identificador genérico do fornecedor do contrato. - - name: objeto_contrato - description: > - Descrição do objeto contratado. - - name: tipolistafatura_id - description: > - Identificador do tipo de lista de fatura. - - name: justificativafatura_id - description: > - Identificador da justificativa da fatura, quando aplicável. - - name: sfadrao_id - description: > - Identificador do padrão de sistema financeiro relacionado. - - name: numero - description: > - Número da fatura emitida pelo fornecedor. - - name: emissao - description: > - Data de emissão da fatura. - - name: prazo - description: > - Data limite para pagamento da fatura. - - name: vencimento - description: > - Data de vencimento da fatura. - - name: valor - description: > - Valor bruto da fatura. - - name: juros - description: > - Valor de juros aplicados à fatura. - - name: multa - description: > - Valor de multa aplicada à fatura. - - name: glosa - description: > - Valor de glosa aplicada à fatura. - - name: valorliquido - description: > - Valor líquido da fatura após subtrações de glosas, multas ou juros. - - name: processo - description: > - Número do processo administrativo específico da fatura, quando diferente do processo do contrato. - - name: protocolo - description: > - Data de protocolo da fatura no sistema. - - name: ateste - description: > - Data em que a fatura foi atestada, confirmando a entrega do produto ou serviço. - - name: repactuacao - description: > - Indica se a fatura está relacionada a uma repactuação contratual. - - name: infcomplementar - description: > - Informações complementares sobre a fatura. - - name: mesref - description: > - Mês de referência da fatura. - - name: anoref - description: > - Ano de referência da fatura. - - name: situacao - description: > - Status atual da fatura (Pago ou Pendente). - - name: chave_nfe - description: > - Chave da Nota Fiscal Eletrônica associada à fatura, quando disponível. - - name: dados_referencia - description: > - Dados de referência adicionais da fatura, geralmente em formato JSON. - - name: dados_item_faturado - description: > - Detalhamento dos itens faturados, geralmente em formato JSON. - - name: dados_empenho - description: > - Informações sobre o empenho relacionado à fatura, em formato JSON. - - name: id_empenho - description: > - Identificador do empenho extraído do campo JSON dados_empenho. - - name: numero_empenho - description: > - Número do empenho relacionado à fatura. - - name: valor_empenho - description: > - Valor do empenho relacionado à fatura. - - name: subelemento - description: > - Código do subelemento de despesa relacionado à fatura. - - - name: cronogramas_faturas_mensal - description: > - Essa tabela discrimina os valores programados e faturados mensalmente pelo ComprasGov para cada contrato. - Combina dados dos cronogramas com as faturas pagas e pendentes, agrupando por contrato e mês de referência, - e calculando o saldo contratual disponível (diferença entre o valor programado e os valores faturados). - meta: - tags: - - silver - columns: - - name: contrato_id - description: > - Identificador único do contrato. - - name: mes_ref - description: > - Mês de referência dos valores programados e faturados. - - name: valor_cronograma - description: > - Valor total programado para o mês de referência conforme cronograma do contrato. - - name: valor_faturas_pagas - description: > - Valor total de faturas com status "Pago" no mês de referência. - - name: valor_faturas_pendentes - description: > - Valor total de faturas com status "Pendente" no mês de referência. - - name: saldo_contratual_disponivel - description: > - Diferença entre o valor programado no cronograma e a soma dos valores faturados (pagos e pendentes). - - - - ## Golds - - name: contratos_resumo - description: > - Essa tabela contém um resumo dos contratos, informações contratuais como valor global e valor pago, - e situação atual, como em vigência ou pendente de baixa. - Facilita a análise gerencial dos contratos com indicadores chave como status de pagamento, tipo de fornecedor, - e identificação de contratos continuados (com vigência superior a dois anos e mais de uma parcela). - meta: - tags: - - gold - columns: - - name: contrato_id - description: > - Identificador único do contrato. - - name: fornecedor_cnpj_cpf - description: > - CNPJ ou CPF do fornecedor contratado, com formatação padronizada. - - name: numero - description: > - Número do contrato no sistema ComprasGov. - - name: categoria - description: > - Categoria do contrato ( Informática, Serviços, Mão de Obra). - - name: modalidade - description: > - Modalidade de licitação utilizada na contratação. - - name: tipo - description: > - Tipo de contrato ( Contrato, Empenho, Termo de Compromisso). - - name: situacao - description: > - Situação atual do contrato (Ativo ou Inativo). - - name: pendente_baixa - description: > - Indica se o contrato está pendente de baixa ('Sim' quando o valor pago for igual ao valor global, 'Não' caso contrário). - - name: fornecedor_nome - description: > - Nome ou razão social do fornecedor contratado. - - name: objeto - description: > - Descrição detalhada do objeto contratado. - - name: valor_global - description: > - Valor total do contrato após eventuais aditivos. - - name: despesas_pagas - description: > - Valor total já pago do contrato conforme registros do SIAFI. - - name: vigencia_inicio - description: > - Data de início da vigência do contrato. - - name: vigencia_fim - description: > - Data de término da vigência do contrato. - - name: num_parcelas - description: > - Número de parcelas para pagamento do contrato. - - name: fornecedor_tipo - description: > - Tipo do fornecedor, categorizado como 'Empresa do Exterior' para IDGENERICO. - - name: Unidade - description: > - Unidade gestora responsável pelo contrato, no formato "código - nome_resumido". - - name: continuado - description: > - Indica se o contrato é de prestação continuada ('Sim' quando a vigência for maior que 730 dias e tiver mais de uma parcela). - - - name: contratos_comparativo_mensal - description: > - Essa tabela contém um comparativo mensal dos contratos, discriminando - os valores empenhados, liquidados e pagos do SIAFI, - programados e faturados do ComprasGov. - Permite a identificação de inconsistências entre os sistemas e o acompanhamento detalhado - da execução financeira mensal de cada contrato. - meta: - tags: - - gold - columns: - - name: contrato_id - description: > - Identificador único do contrato. - - name: mes_ref - description: > - Mês de referência da informação financeira, preenchido para todos os meses entre o início e fim do contrato. - - name: comprasgov_valor_cronograma - description: > - Valor programado para o mês conforme cronograma registrado no ComprasGov. - - name: comprasgov_valor_faturas - description: > - Soma dos valores das faturas (pagas e pendentes) no mês de referência conforme ComprasGov. - - name: comprasgov_saldo_contratual_disponivel - description: > - Diferença entre o valor programado e o valor faturado no mês, indicando o saldo disponível. - - name: siafi_valor_empenhado - description: > - Valor empenhado no mês de referência conforme registros do SIAFI. - - name: siafi_valor_liquidado - description: > - Valor liquidado no mês de referência conforme registros do SIAFI. - - name: siafi_valor_pago - description: > - Valor pago no mês de referência conforme registros do SIAFI. - - - name: contratos_somatorio - description: > - Essa tabela contém somatórios dos valores de cronograma, fatura, empenho, liquidação e pagamento para cada contrato. - Facilita análises agregadas sobre a execução financeira total de cada contrato, com indicadores importantes - como o orçamento pendente de execução. - meta: - tags: - - gold - columns: - - name: contrato_id - description: > - Identificador único do contrato. - - name: total_cronograma - description: > - Soma de todos os valores programados no cronograma do contrato. - - name: total_faturas - description: > - Soma de todos os valores faturados (pagos e pendentes) para o contrato. - - name: total_saldo_disponivel - description: > - Diferença total entre valores programados e faturados, indicando o saldo contratual disponível. - - name: orcamento_a_executar - description: > - Soma dos valores programados em meses onde ainda não houve faturamento, indicando o orçamento pendente de execução. - - name: total_empenhado - description: > - Soma de todos os valores empenhados para o contrato segundo o SIAFI. - - name: total_liquidado - description: > - Soma de todos os valores liquidados para o contrato segundo o SIAFI. - - name: total_pago - description: > - Soma de todos os valores pagos para o contrato segundo o SIAFI. - - ## View - - name: identificadores - description: > - Essa tabela contém os identificadores dos contratos, empenhos e faturas. - Consolida os números de empenho encontrados em diferentes fontes (contratos, faturas, empenhos) - e padroniza informações como processo e CNPJ/CPF para facilitar joins entre as tabelas. - Também cria um campo info_complementar que combina unidade gestora, modalidade e número de contrato/licitação. - columns: - - name: contrato_id - description: > - Identificador único do contrato. - - name: categoria - description: > - Categoria do contrato ( Informática, Serviços, Mão de Obra). - - name: processo - description: > - Número do processo administrativo do contrato, formatado para remover caracteres não numéricos. - - name: cnpj_cpf - description: > - CNPJ ou CPF do fornecedor, formatado para remover caracteres especiais como barras, pontos e hífens. - - name: info_complementar - description: > - Informação complementar construída a partir da unidade gestora, código de modalidade e número do contrato ou licitação. - - name: ne - description: > - Número da nota de empenho relacionada ao contrato, combinando informações de contratos, empenhos e faturas. - - - - # Pessoas DBT - - # TED DBT - - name: pf_tesouro - description: > - Esta tabela contém registros de Programações Financeiras extraídas do SIAFI, detalhando informações como tipo de programação, data de execução e valor. - A tabela é atualizada diariamente e reflete as autorizações de movimentação financeira no âmbito da execução orçamentária federal. - meta: - tags: - - bronze - columns: - - name: emissao_mes - description: > - Mês de emissão da Programação Financeira. - - name: emissao_dia - description: > - Dia de emissão da Programação Financeira. - - name: ug_emitente - description: > - Código da Unidade Gestora responsável pela emissão da Programação Financeira. - - name: ug_emitente_descricao - description: > - Nome ou descrição da Unidade Gestora emitente da Programação Financeira. - - name: ug_favorecido - description: > - Código da Unidade Gestora favorecida pela Programação Financeira. - - name: ug_favorecido_descricao - description: > - Nome ou descrição da Unidade Gestora favorecida pela Programação Financeira. - - name: pf_evento - description: > - Código do evento contábil associado à Programação Financeira, representando a natureza da transação. - - name: pf_evento_descricao - description: > - Descrição do evento contábil vinculado à Programação Financeira. - - name: pf - description: > - Número identificador único da Programação Financeira. - - name: pf_inscricao - description: > - Número de inscrição da Programação Financeira, utilizado para controle e acompanhamento. - - name: pf_acao - description: > - Código da ação orçamentária associada à Programação Financeira. - - name: pf_acao_descricao - description: > - Descrição da ação orçamentária vinculada à Programação Financeira. - - name: pf_fonte_recursos - description: > - Código da fonte de recursos associada à Programação Financeira, indicando a origem dos recursos utilizados. - - name: pf_fonte_recursos_descricao - description: > - Descrição textual da fonte de recursos vinculada à Programação Financeira. - - name: pf_vinculacao_pagamento - description: > - Código da vinculação de pagamento associada à Programação Financeira. - - name: pf_vinculacao_pagamento_descricao - description: > - Descrição da vinculação de pagamento vinculada à Programação Financeira. - - name: pf_categoria_gasto - description: > - Código da categoria de gasto associada à Programação Financeira, conforme classificação orçamentária. - - name: pf_recurso - description: > - Código do recurso associado à Programação Financeira. - - name: pf_recurso_descricao - description: > - Descrição do recurso vinculado à Programação Financeira. - - name: doc_observacao - description: > - Observações adicionais relacionadas à Programação Financeira. - - name: pf_valor_linha - description: > - Valor monetário individual da linha da Programação Financeira. - data_tests: - - verificacao_tipagem: - nome_tabela: 'ted.pf_tesouro' - nome_coluna: 'emissao_dia' - tipo_esperado: 'date' - - verificacao_tipagem: - nome_tabela: 'ted.pf_tesouro' - nome_coluna: 'pf_valor_linha' - tipo_esperado: 'numeric' - - - name: nc_tesouro - description: > - Esta tabela registra informações detalhadas sobre as Notas de Crédito emitidas no âmbito da execução orçamentária e financeira do governo federal. - As Notas de Crédito representam autorizações para a realização de despesas, sendo fundamentais para o controle e acompanhamento da execução orçamentária. - meta: - tags: - - bronze - columns: - - name: emissao_mes - description: > - Mês de emissão da Nota de Crédito. - - name: emissao_dia - description: > - Dia de emissão da Nota de Crédito. - - name: nc - description: > - Número identificador único da Nota de Crédito. - - name: nc_transferencia - description: > - Indicador de que a Nota de Crédito refere-se a uma transferência de recursos entre unidades gestoras. - - name: nc_fonte_recursos - description: > - Código da fonte de recursos associada à Nota de Crédito, indicando a origem dos recursos utilizados. - - name: nc_fonte_recursos_descricao - description: > - Descrição textual da fonte de recursos vinculada à Nota de Crédito. - - name: ptres - description: > - Código do Plano Interno de Trabalho (PTRES) relacionado à Nota de Crédito, utilizado para detalhar a alocação dos recursos. - - name: nc_evento - description: > - Código do evento contábil associado à Nota de Crédito, representando a natureza da transação. - - name: nc_evento_descricao - description: > - Descrição do evento contábil vinculado à Nota de Crédito. - - name: ug_responsavel - description: > - Código da Unidade Gestora responsável pela emissão da Nota de Crédito. - - name: ug_responsavel_descricao - description: > - Nome ou descrição da Unidade Gestora responsável pela emissão da Nota de Crédito. - - name: natureza_despesa - description: > - Código da natureza da despesa associada à Nota de Crédito, conforme classificação orçamentária. - - name: natureza_despesa_detalhada - description: > - Descrição detalhada da natureza da despesa vinculada à Nota de Crédito. - - name: plano_interno - description: > - Código do plano interno relacionado à Nota de Crédito, utilizado para controle interno da execução orçamentária. - - name: plano_detalhado_descricao1 - description: > - Primeira descrição detalhada do plano interno associado à Nota de Crédito. - - name: plano_detalhado_descricao2 - description: > - Segunda descrição detalhada do plano interno associado à Nota de Crédito. - - name: favorecido_doc - description: > - Documento de identificação (CPF ou CNPJ) do favorecido pela Nota de Crédito. - - name: favorecido_doc_descricao - description: > - Nome ou razão social do favorecido identificado na Nota de Crédito. - - name: nc_valor_linha - description: > - Valor monetário individual da linha da Nota de Crédito. - - name: movimento_liquido - description: > - Valor líquido do movimento financeiro associado à Nota de Crédito, após deduções ou acréscimos aplicáveis. - - - name: planos_acao - description: > - Esta tabela armazena informações detalhadas sobre os Planos de Ação vinculados a programas públicos. - Ela consolida dados sobre as unidades envolvidas na execução, prazos de vigência, valores, justificativas, - formas de execução e instrumentos utilizados. Serve como base para o acompanhamento, análise e auditoria - da implementação de ações públicas em diferentes formatos de execução. - meta: - tags: - - bronze - columns: - - name: id_plano_acao - description: > - Identificador único do Plano de Ação. - - name: id_programa - description: > - Identificador único do programa ao qual o Plano de Ação está vinculado. - - name: sigla_unidade_descentralizada - description: > - Sigla da unidade descentralizada responsável pelo Plano de Ação. - - name: unidade_descentralizada - description: > - Nome completo da unidade descentralizada responsável pelo Plano de Ação. - - name: sigla_unidade_responsavel_execucao - description: > - Sigla da unidade responsável pela execução do Plano de Ação. - - name: unidade_responsavel_execucao - description: > - Nome completo da unidade responsável pela execução do Plano de Ação. - - name: vl_total_plano_acao - description: > - Valor total previsto para execução do Plano de Ação. - - name: dt_inicio_vigencia - description: > - Data de início da vigência do Plano de Ação. - - name: dt_fim_vigencia - description: > - Data final da vigência do Plano de Ação. - - name: tx_objeto_plano_acao - description: > - Descrição do objeto do Plano de Ação, informando seu propósito e escopo. - - name: tx_justificativa_plano_acao - description: > - Justificativa para a elaboração e execução do Plano de Ação. - - name: in_forma_execucao_direta - description: > - Indicador booleano que sinaliza se a execução do plano ocorrerá de forma direta. - - name: in_forma_execucao_particulares - description: > - Indicador booleano que sinaliza se a execução contará com a participação de particulares. - - name: in_forma_execucao_descentralizada - description: > - Indicador booleano que sinaliza se a execução será realizada de forma descentralizada. - - name: tx_situacao_plano_acao - description: > - Situação atual do Plano de Ação ( em elaboração, aprovado, em execução, concluído). - - name: aa_ano_plano_acao - description: > - Ano de referência do Plano de Ação. - - name: vl_beneficiario_especifico - description: > - Valor destinado especificamente a beneficiários definidos no Plano de Ação. - - name: vl_chamamento_publico - description: > - Valor previsto para execução por meio de chamamento público. - - name: sq_instrumento - description: > - Código sequencial do instrumento jurídico ou administrativo associado ao Plano de Ação. - - name: aa_instrumento - description: > - Ano de referência do instrumento associado ao Plano de Ação. - - - name: nota_credito - description: > - Esta tabela contém informações sobre as Notas de Crédito emitidas no contexto dos Planos de Ação. - As notas de crédito representam movimentações financeiras entre unidades gestoras e gestões, sendo - vinculadas a um Plano de Ação específico. A tabela registra dados como número, data de emissão, - códigos das unidades e gestões envolvidas, além de observações e situação da nota. - meta: - tags: - - bronze - columns: - - name: id_nota - description: > - Identificador único da Nota de Crédito. - - name: id_plano_acao - description: > - Identificador único do Plano de Ação ao qual a Nota de Crédito está vinculada. - - name: tx_minuta_nota - description: > - Texto ou rascunho da minuta da Nota de Crédito. - - name: tx_numero_nota - description: > - Número da Nota de Crédito, utilizado para controle e rastreabilidade. - - name: dt_emissao_nota - description: > - Data e hora da emissão da Nota de Crédito. - - name: cd_gestao_emitente_nota - description: > - Código da gestão pública que emitiu a Nota de Crédito. - - name: cd_gestao_favorecida_nota - description: > - Código da gestão pública favorecida pela Nota de Crédito. - - name: tx_situacao_nota - description: > - Situação atual da Nota de Crédito ( emitida, cancelada, aprovada). - - name: cd_ug_emitente_nota - description: > - Código da Unidade Gestora que emitiu a Nota de Crédito. - - name: cd_ug_favorecida_nota - description: > - Código da Unidade Gestora favorecida pela Nota de Crédito. - - name: tx_observacao_nota - description: > - Observações adicionais registradas na Nota de Crédito, quando houver. - - - name: programacao_financeira - description: > - Esta tabela armazena os registros de Programações Financeiras vinculadas aos Planos de Ação. - A Programação Financeira representa o planejamento e execução da alocação de recursos entre unidades gestoras. - A tabela contempla informações como tipo, número, situação, unidade emitente e favorecida, - bem como observações e o documento hábil de recebimento. - meta: - tags: - - bronze - columns: - - name: id_programacao - description: > - Identificador único da Programação Financeira. - - name: id_plano_acao - description: > - Identificador único do Plano de Ação ao qual a Programação Financeira está vinculada. - - name: tp_pf_tipo_programacao - description: > - Tipo de Programação Financeira ( ordinária, suplementar, especial). - - name: tx_minuta_programacao - description: > - Minuta ou rascunho da Programação Financeira. - - name: tx_numero_programacao - description: > - Número da Programação Financeira, utilizado para controle interno e rastreamento. - - name: tx_situacao_programacao - description: > - Situação atual da Programação Financeira ( em análise, aprovada, cancelada). - - name: tx_observacao_programacao - description: > - Observações complementares sobre a Programação Financeira, se houver. - - name: ug_emitente_programacao - description: > - Código ou nome da Unidade Gestora que emitiu a Programação Financeira. - - name: ug_favorecida_programacao - description: > - Código ou nome da Unidade Gestora favorecida pela Programação Financeira. - - name: dh_recebimento_programacao - description: > - Data e hora do recebimento do documento hábil relacionado à Programação Financeira. - - - name: programas - description: > - Esta tabela contém os registros dos Programas institucionais, que orientam e organizam os Planos de Ação - sob responsabilidade de diferentes unidades da administração pública. A tabela agrega dados como código, - nome, ano, situação, descrição, objetivos e informações relacionadas às autorizações e tipos de investimentos, - além de indicar critérios específicos como beneficiários e chamamentos públicos. - meta: - tags: - - bronze - columns: - - name: id_programa - description: > - Identificador único do Programa. - - name: tx_codigo_programa - description: > - Código atribuído ao Programa para fins de controle e identificação institucional. - - name: aa_ano_programa - description: > - Ano de referência do Programa. - - name: tx_situacao_programa - description: > - Situação atual do Programa ( ativo, encerrado, em elaboração). - - name: tx_nome_programa - description: > - Nome oficial do Programa. - - name: sigla_unidade_descentralizadora - description: > - Sigla da unidade responsável pela descentralização dos recursos do Programa. - - name: unidade_descentralizadora - description: > - Nome completo da unidade descentralizadora responsável pelo Programa. - - name: sigla_unidade_responsavel_acompanhamento - description: > - Sigla da unidade responsável pelo acompanhamento do Programa. - - name: unidade_responsavel_acompanhamento - description: > - Nome completo da unidade responsável pelo acompanhamento do Programa. - - name: tx_nome_institucional_programa - description: > - Nome institucional utilizado oficialmente para identificar o Programa. - - name: tx_objetivo_programa - description: > - Objetivo principal do Programa, descrevendo seu propósito estratégico. - - name: tx_descricao_programa - description: > - Descrição detalhada do Programa, abrangendo seu escopo e diretrizes. - - name: in_grupo_investimento_obra - description: > - Indicador booleano que sinaliza se o Programa está vinculado a investimento em obras. - - name: in_grupo_investimento_servico - description: > - Indicador booleano que sinaliza se o Programa está vinculado a investimento em serviços. - - name: in_grupo_investimento_equipamento - description: > - Indicador booleano que sinaliza se o Programa está vinculado a investimento em equipamentos. - - name: in_autoriza_subdescentralizacao_outro - description: > - Indicador que informa se o Programa autoriza subdescentralização para outras unidades. - - name: in_autoriza_realizacao_despesas - description: > - Indicador que informa se o Programa autoriza a realização de despesas diretamente. - - name: in_autoriza_execucao_creditos_descentralizada - description: > - Indicador que informa se o Programa autoriza a execução descentralizada de créditos. - - name: in_beneficiario_especifico - description: > - Indicador booleano de existência de beneficiário específico no Programa. - - name: dt_recebimento_plano_beneficiario_inicio - description: > - Data de início para recebimento do plano de beneficiário específico vinculado ao Programa. - - name: dt_recebimento_plano_beneficiario_fim - description: > - Data final para recebimento do plano de beneficiário específico vinculado ao Programa. - - name: in_chamamento_publico - description: > - Indicador booleano de execução via chamamento público no âmbito do Programa. - - name: dt_recebimento_plano_chamamento_inicio - description: > - Data de início para recebimento do plano relacionado a chamamento público. - - name: dt_recebimento_plano_chamamento_fim - description: > - Data final para recebimento do plano relacionado a chamamento público. - - - name: empenhos_plano_acao - description: > - Esta tabela estabelece a relação entre os empenhos registrados no SIAFI e os respectivos Planos de Ação, - permitindo o rastreamento das despesas públicas desde a origem do crédito até sua execução. - meta: - tags: - - silver - columns: - - name: ne - description: > - Número da Nota de Empenho, extraído dos 12 últimos dígitos do campo `ne_ccor`, representando o identificador do empenho no SIAFI. - - name: num_transf - description: > - Número da transferência identificado na descrição da nota de empenho (`ne_ccor_descricao`), utilizado para vincular o empenho ao Plano de Ação correspondente. - - name: nc - description: > - Código da Nota de Crédito associado ao empenho, extraído da descrição da nota de empenho e formatado conforme padrão estabelecido. - - name: plano_acao - description: > - Identificador do Plano de Ação relacionado ao empenho, obtido a partir da correspondência com o número de transferência (`num_transf`). - - name: ne_ccor - description: > - Campo original contendo o código completo da Nota de Empenho, utilizado como base para extração de identificadores. - - name: ne_ccor_descricao - description: > - Descrição textual da Nota de Empenho, de onde são extraídos os números de transferência e códigos de nota de crédito para associação com os Planos de Ação. - - name: demais_colunas - description: > - Outras colunas provenientes da tabela `empenhos_tesouro`, contendo informações adicionais sobre os empenhos, como data de emissão, valor, unidade gestora, entre outras. - - - name: nc_plano_acao - description: > - Esta tabela estabelece a relação entre as Notas de Crédito registradas no SIAFI e os respectivos Planos de Ação, - permitindo o rastreamento das movimentações financeiras desde a origem do crédito até sua aplicação. - meta: - tags: - - silver - columns: - - name: plano_acao - description: > - Identificador do Plano de Ação relacionado à Nota de Crédito, obtido a partir da correspondência com o número de transferência (`nc_transferencia`). - - name: emissao_dia - description: > - Dia de emissão da Nota de Crédito. - - name: num_transf - description: > - Número da transferência associado à Nota de Crédito, utilizado para vincular a movimentação financeira ao Plano de Ação correspondente. - - name: nc - description: > - Código da Nota de Crédito, extraído dos 12 últimos dígitos do campo `nc`, representando o identificador da nota no SIAFI. - - name: nc_fonte_recursos - description: > - Código da fonte de recursos associada à Nota de Crédito, indicando a origem dos recursos utilizados. - - name: ptres - description: > - Código do Plano Interno de Trabalho (PTRES) vinculado à Nota de Crédito, representando a ação orçamentária correspondente. - - name: ug_emitente - description: > - Código da Unidade Gestora emitente da Nota de Crédito, extraído dos 6 primeiros dígitos do campo `nc`. - - name: nc_natureza_despesa - description: > - Código da natureza da despesa associada à Nota de Crédito, conforme classificação orçamentária. - - name: nc_evento - description: > - Código do evento contábil associado à Nota de Crédito, representando a natureza da transação. - - name: nc_evento_descr - description: > - Descrição do evento contábil vinculado à Nota de Crédito. - - name: nc_valor - description: > - Valor monetário da Nota de Crédito, ajustado conforme o tipo de evento contábil. - - - name: pf_plano_acao - description: > - Esta tabela consolida as programações financeiras executadas no âmbito do SIAFI que estão relacionadas a Planos de Ação. - meta: - tags: - - silver - columns: - - name: pf - description: > - Número identificador da Programação Financeira, conforme registrado no SIAFI. - - name: num_transf - description: > - Número da transferência extraído do campo `pf_inscricao`, utilizado como chave alternativa para vinculação com Planos de Ação. - - name: emissao_mes - description: > - Mês da emissão da Programação Financeira, no formato numérico (1 a 12). - - name: emissao_dia - description: > - Dia da emissão da Programação Financeira, no formato numérico (1 a 31). - - name: ug_emitente - description: > - Código da Unidade Gestora responsável pela emissão da Programação Financeira. - - name: ug_favorecido - description: > - Código da Unidade Gestora favorecida pela Programação Financeira. - - name: pf_evento - description: > - Código do evento contábil associado à Programação Financeira, que descreve o tipo de movimentação registrada. - - name: pf_evento_descricao - description: > - Descrição do evento contábil da Programação Financeira, fornecendo contexto adicional sobre o tipo de operação realizada. - - name: pf_acao - description: > - Código da ação orçamentária extraído da descrição da programação financeira, representando a finalidade da despesa. - - name: pf_valor_linha - description: > - Valor programado para execução financeira, referente à linha específica da Programação Financeira. - - name: plano_acao - description: > - Identificador do Plano de Ação associado à Programação Financeira, determinado por correspondência direta com o sistema TransfereGov ou via número de transferência. - - - name: ted_resumo_orcamentario - description: > - Tabela de consolidação orçamentária e financeira por Plano de Ação, baseada em informações de valores firmados, orçamentos recebidos e devolvidos, empenhos, despesas e transferências financeiras. - Os dados são integrados de diferentes fontes (notas de crédito, empenhos e programações financeiras) para fornecer um resumo abrangente da execução orçamentária e financeira de transferências intergovernamentais. - meta: - tags: - - gold - columns: - - name: plano_acao - description: > - Identificador único do Plano de Ação associado aos registros orçamentários e financeiros. - - name: num_transf - description: > - Número da transferência utilizado como chave de ligação entre diferentes fontes de dados. - - name: sigla_unidade_descentralizada - description: > - Sigla da Unidade Descentralizada responsável pelo Plano de Ação. - - name: ted_beneficiario_emitente - description: > - Identifica se a Unidade do Plano de Ação é a beneficiária ou a emitente dos recursos da TED (Transferência Voluntária). Valores possíveis: 'beneficiario', 'emitente' ou 'nao_indicado'. - - name: valor_firmado - description: > - Valor total originalmente acordado no Plano de Ação como meta de transferência de recursos. - - name: orcamento_recebido - description: > - Total de créditos orçamentários efetivamente recebidos por meio de Notas de Crédito. - - name: orcamento_devolvido - description: > - Total de créditos orçamentários devolvidos, identificado por eventos contábeis específicos (ex.: 300301, 300307). - - name: empenhado - description: > - Soma dos valores empenhados, ou seja, das despesas formalizadas no orçamento, excluindo anulações. - - name: empenho_anulado - description: > - Total de valores de empenhos anulados, ou seja, revertidos após sua emissão. - - name: despesas_pagas_exercicio - description: > - Total de despesas pagas no exercício corrente. - - name: despesas_pagas_rap - description: > - Total de despesas pagas com recursos de Restos a Pagar. - - name: restos_a_pagar - description: > - Valor total inscrito como Restos a Pagar, ou seja, despesas empenhadas e não pagas até o fim do exercício. - - name: despesas_liquidada - description: > - Soma das despesas liquidadas, indicando bens ou serviços efetivamente entregues. - - name: financeiro_recebido - description: > - Total de recursos financeiros recebidos via Programação Financeira (TED), considerando apenas ações classificadas como 'TRANSFERENCIA'. - - name: financeiro_devolvido - description: > - Total de recursos financeiros devolvidos, com ações classificadas como 'DEVOLUCAO'. - - name: financeiro_cancelado - description: > - Valor total de cancelamentos financeiros identificados na programação, com ação 'CANCELAMENTO'. - - - name: num_transf_n_plano_acao - description: > - View responsável por mapear o número de transferência (`num_transf`) ao respectivo Plano de Ação (`plano_acao`). - Utiliza dados de Notas de Crédito do TransfereGov e do SIAFI para realizar o cruzamento entre diferentes sistemas. - columns: - - name: num_transf - description: > - Número da transferência financeira obtido a partir dos registros do SIAFI (nota de crédito). Serve como chave para integrar informações entre diferentes fontes, como notas de crédito, programações financeiras e empenhos. - - name: plano_acao - description: > - Identificador único do Plano de Ação, conforme registrado no TransfereGov. É atribuído ao `num_transf` com base no cruzamento entre a nota de crédito (NC) e a unidade gestora emitente (UG). - - # Bronze - - name: afastamento_historico - description: > - Tabela bronze que armazena o histórico de afastamentos dos servidores. - Contém informações detalhadas sobre cada período de afastamento, incluindo datas, tipo, e amparo legal. - Os dados são limpos e padronizados a partir da fonte original. - meta: - tags: - - bronze - columns: - - name: adiantamento_salario_ferias - description: "Indica se houve adiantamento de salário durante as férias." - - name: ano_exercicio - description: "Ano de exercício a que o afastamento se refere." - - name: dt_fim - description: "Data de término do afastamento." - - name: dt_fim_aquisicao - description: "Data final do período aquisitivo de férias." - - name: dt_ini - description: "Data de início do afastamento." - - name: dt_inicio_aquisicao - description: "Data inicial do período aquisitivo de férias." - - name: dt_inicio_ferias_interrompidas - description: "Data de início de férias que foram interrompidas." - - name: dias_restantes - description: "Dias restantes de afastamento." - - name: gratificacao_natalina - description: "Indica se o afastamento está relacionado à gratificação natalina." - - name: numero_parcela - description: "Número da parcela do afastamento." - - name: parcela_continuacao_interrupcao - description: "Indica se a parcela é uma continuação ou interrupção." - - name: parcelainterrompida - description: "Indica se a parcela foi interrompida." - - name: qtde_dias - description: "Quantidade de dias do afastamento." - - name: cpf - description: "CPF do servidor." - - name: cod_diploma_afastamento - description: "Código do diploma legal do afastamento." - - name: cod_ocorrencia - description: "Código da ocorrência do afastamento." - - name: dt_publicacao_afastamento - description: "Data de publicação do afastamento." - - name: desc_diploma_afastamento - description: "Descrição do diploma legal do afastamento." - - name: desc_ocorrencia - description: "Descrição da ocorrência do afastamento." - - name: numero_diploma_afastamento - description: "Número do diploma legal do afastamento." - - - - name: cargos_funcoes - description: > - Tabela bronze que representa as funções e cargos extraídos do sistema SIORG. - Ela contém informações sobre o código e nome do cargo/função, nível hierárquico, - categoria funcional, regras de autoridade, além de dados normativos (ato normativo) - e as diversas denominações associadas a cada cargo/função. - Os dados são expandidos a partir de arrays JSON para facilitar a análise. - meta: - tags: - - bronze - columns: - - name: codigotipo - description: "Código que representa o tipo do cargo ou função." - - - name: nome - description: "Nome completo do cargo ou função." - - - name: sigla - description: "Sigla identificadora do cargo ou função." - - - name: codigocargofuncao - description: "Código único que identifica o cargo ou função." - - - name: categoria - description: "Categoria funcional do cargo, como direção, assessoramento, etc." - - - name: nivel - description: "Nível hierárquico do cargo ou função." - - - name: atonormativo__tipoato - description: "Tipo do ato normativo que criou ou regulamenta o cargo/função." - - - name: atonormativo__codigounidade - description: "Código da unidade responsável pelo ato normativo." - - - name: atonormativo__numero - description: "Número do ato normativo relacionado ao cargo ou função." - - - name: atonormativo__dataassinatura - description: "Data em que o ato normativo foi assinado." - - - name: atonormativo__datapublicacao - description: "Data de publicação do ato normativo no diário oficial." - - - name: atonormativo__datavigencia - description: "Data em que o ato normativo passou a vigorar." - - - name: atonormativo__ementa - description: "Ementa ou resumo descritivo do ato normativo." - - - name: atonormativo__url - description: "URL de acesso ao conteúdo completo do ato normativo." - - - name: atonormativo__codigotipo - description: "Código que representa o tipo de ato normativo." - - - name: atonormativo__siglatipo - description: "Sigla que representa o tipo de ato normativo." - - - name: denominacao_codigo - description: "Código individual da denominação expandida a partir do array JSON." - - - name: denominacao_descricao - description: "Descrição da denominação expandida para o cargo/função." - - - - name: dados_afastamento - description: > - Tabela bronze com dados atuais de afastamentos dos servidores. - meta: - tags: - - bronze - columns: - - name: adiantamento_salario_ferias - description: "Indica se houve adiantamento de salário durante as férias." - - name: ano_exercicio - description: "Ano de exercício a que o afastamento se refere." - - name: cod_diploma_afastamento - description: "Código do diploma legal do afastamento." - - name: cod_ocorrencia - description: "Código da ocorrência do afastamento." - - name: desc_diploma_afastamento - description: "Descrição do diploma legal do afastamento." - - name: desc_ocorrencia - description: "Descrição da ocorrência do afastamento." - - name: dt_fim - description: "Data de término do afastamento." - - name: dt_ini - description: "Data de início do afastamento." - - name: dt_inicio_aquisicao - description: "Data inicial do período aquisitivo de férias." - - name: dt_publicacao_afastamento - description: "Data de publicação do afastamento." - - name: dias_restantes - description: "Dias restantes de afastamento." - - name: gratificacao_natalina - description: "Indica se o afastamento está relacionado à gratificação natalina." - - name: gr_matricula - description: "Matrícula GR." - - name: numero_diploma_afastamento - description: "Número do diploma legal do afastamento." - - name: numero_parcela - description: "Número da parcela do afastamento." - - name: parcela_continuacao_interrupcao - description: "Indica se a parcela é uma continuação ou interrupção." - - name: qtde_dias - description: "Quantidade de dias do afastamento." - - name: cpf - description: "CPF do servidor." - - name: dt_fim_aquisicao - description: "Data final do período aquisitivo de férias." - - - - name: dados_curriculo - description: > - Tabela bronze que representa dados curriculares e experiências profissionais dos servidores públicos. - Inclui informações sobre cursos realizados, instituições de ensino, tempo de experiência, projetos desenvolvidos e vínculos com órgãos ou empresas. - As datas foram padronizadas para o primeiro dia do mês (`dt_mes_`) e os dados textuais foram limpos de caracteres inválidos. - meta: - tags: - - bronze - columns: - - name: cpf - description: "CPF do servidor, formatado apenas com números." - - - name: ident_unica - description: "Identificador único do servidor no sistema, usado para correlacionar registros." - - - name: codigo_experiencia - description: "Código associado ao tipo de experiência do servidor ( 1 para formação, 2 para experiência profissional)." - - - name: cod_curso - description: "Código do curso realizado, se disponível." - - - name: nome_curso - description: "Nome do curso de formação acadêmica ou técnica. Exemplo: 'Ciências Contábeis', 'Administração'." - - - name: dt_mes_conclusao - description: "Data de conclusão do curso, normalizada para o primeiro dia do mês. Exemplo: '1991-12-01'." - - - name: nome_instituicao - description: "Nome da instituição onde o curso foi realizado. Exemplo: 'UDF', 'Universidade Católica de Brasília'." - - - name: nome_area_experiencia - description: > - Grau ou situação da experiência/capacitação. Pode indicar o nível ou status como: - 'Concluído', 'Intermediário', 'Básico', 'Curso', 'Avançado'." - - - name: carga_horaria - description: "Carga horária do curso, se disponível. Armazenado como texto." - - - name: nome_cargo - description: "Nome do cargo ou função exercida durante a experiência profissional." - - - name: dt_mes_inicio - description: "Data de início da experiência profissional, no formato 'YYYY-MM-01'." - - - name: nome_orgao_empresa - description: "Nome da organização, empresa ou órgão público em que a experiência foi realizada." - - - name: dt_mes_fim - description: "Data de término da experiência profissional, normalizada para o primeiro dia do mês." - - - name: descricao_projeto - description: "Descrição de projetos relevantes associados à experiência." - - - name: informacoes_adicionais - description: "Informações complementares que detalham a experiência ou formação." - - - name: tipo_descricao - description: "Tipo da descrição curricular. Pode indicar se o registro refere-se a curso, experiência, projeto etc." - - - - name: dados_dependentes - description: > - Tabela bronze que contém informações sobre os dependentes dos servidores públicos. - Inclui grau de parentesco, condições de dependência, benefícios associados e o período em que a dependência esteve ativa. - Os dados foram tratados para remover valores inválidos como 'NaN' e '00000000', e datas foram convertidas para o formato `DATE`. - meta: - tags: - - bronze - columns: - - name: cod_condicao - description: > - Código que representa a condição da dependência ( dependente econômico, legal, etc.). - Usado para classificar o tipo de vínculo do dependente com o servidor. - - - name: cod_grau_parentesco - description: > - Código que indica o grau de parentesco do dependente com o servidor ( filho, cônjuge, pai, etc.). - - - name: cod_orgao - description: "Código do órgão ao qual o servidor está vinculado." - - - name: cpf - description: "CPF do servidor titular da matrícula, contendo apenas números." - - - name: matricula - description: "Número da matrícula funcional do servidor ao qual o dependente está associado." - - - name: nome_dependente - description: "Nome completo do dependente." - - - name: nome_condicao - description: > - Descrição da condição do dependente ( 'Dependente para IR', 'Dependente para Plano de Saúde'). - - - name: nome_grau_parentesco - description: > - Descrição textual do grau de parentesco entre o servidor e o dependente ( 'Filho', 'Cônjuge'). - - - name: cod_beneficio - description: "Código que representa o tipo de benefício relacionado ao dependente, se houver." - - - name: dt_fim - description: > - Data de término da condição de dependência, no formato 'DDMMYYYY', convertida para DATE. - Pode ser nula se a dependência ainda estiver ativa. - - - name: dt_inicio - description: > - Data de início da condição de dependência, no formato 'DDMMYYYY', convertida para DATE. - - - name: nome_beneficio - description: > - Nome do benefício associado ao dependente ( auxílio-saúde, plano de assistência etc.). - - - - name: dados_escolares - description: > - Tabela bronze que armazena dados sobre a formação educacional dos servidores públicos. - Inclui informações sobre escolaridade, titulação e cursos associados às matrículas funcionais. - Os dados foram limpos para remover valores vazios e o CPF foi padronizado contendo apenas dígitos. - meta: - tags: - - bronze - columns: - - name: cod_curso - description: "Código identificador do curso realizado pelo servidor." - - - name: nome_curso - description: "Nome do curso relacionado à formação do servidor." - - - name: cod_matricula - description: "Código da matrícula funcional do servidor, vinculado ao curso." - - - name: cod_orgao - description: "Código do órgão público ao qual o servidor está vinculado." - - - name: cod_titulacao - description: "Código que representa a titulação do servidor ( especialização, mestrado, doutorado)." - - - name: nome_titulacao - description: "Descrição textual da titulação ( 'Mestrado', 'Doutorado')." - - - name: cod_escolaridade - description: "Código do nível de escolaridade ( ensino médio, superior, técnico)." - - - name: nome_escolaridade - description: "Descrição do nível de escolaridade do servidor." - - - name: cpf - description: "CPF do servidor, contendo apenas números, utilizado para identificação única." - - - - - name: dados_financeiros - description: > - Tabela bronze que armazena dados financeiros associados a servidores públicos. - Contém informações detalhadas sobre rubricas (proventos e descontos), valores pagos, - períodos de referência e data de pagamento. Os dados passam por limpeza e transformação - de formatos monetários e temporais para garantir consistência e padronização. - meta: - tags: - - bronze - columns: - - name: cod_rubrica - description: "Código da rubrica financeira (provento ou desconto) referente ao pagamento do servidor." - - - name: indicador_rd - description: "Indicador que diferencia se a rubrica é de receita (R) ou despesa (D)." - - - name: nome_rubrica - description: "Nome descritivo da rubrica, como 'Salário Base', 'Auxílio Alimentação', etc." - - - name: numero_sequencia - description: "Número sequencial que identifica a ordem da rubrica na folha de pagamento." - - - name: valor_rubrica - description: > - Valor monetário da rubrica, convertido de string para tipo numérico. - Foi realizada limpeza dos pontos e substituição da vírgula decimal por ponto. - - - name: data_anomes_rubrica - description: > - Data correspondente ao mês e ano de referência da rubrica, convertida do formato textual ( 'JAN2020') para uma data no formato YYYY-MM-DD - com o dia fixado em 01. - - - name: prazo_rubrica - description: "Informação de prazo da rubrica (campo opcional e com dados variados, pode indicar vencimento ou parcelamento)." - - - name: mes_ano_pagamento - description: > - Data correspondente ao mês e ano de efetivação do pagamento, convertida do formato textual ( 'JAN2020') para data com o dia fixado em 01. - - - name: cpf - description: > - CPF do servidor, padronizado contendo apenas dígitos. Utilizado para vinculação com outras informações do servidor. - - - name: indicador_mov_supl - description: > - Indicador que mostra se a movimentação financeira se refere a um pagamento suplementar ou retroativo (campo técnico da folha). - - - name: periodo_rubrica - description: > - Período a que se refere a rubrica, podendo representar um agrupamento ou classificação contábil adicional." - - - - name: dados_funcionais - description: > - Tabela bronze que armazena os dados funcionais dos servidores, contendo informações - sobre cargos, funções, regimes jurídicos, ocorrências de ingresso e aposentadoria, e dados - de unidades organizacionais (lotação e exercício). As datas são padronizadas, os CPFs higienizados - e os valores tratados para garantir integridade e legibilidade dos dados. - meta: - tags: - - bronze - columns: - - name: cod_atividade_funcao - description: "Código da atividade ou função exercida pelo servidor." - - - name: cod_funcao - description: "Código identificador da função do servidor." - - - name: cod_jornada - description: "Código da jornada de trabalho do servidor." - - - name: cod_ocorr_ingresso_orgao - description: "Código da ocorrência referente ao ingresso no órgão." - - - name: cod_ocorr_ingresso_serv_publico - description: "Código da ocorrência de ingresso no serviço público federal." - - - name: cod_orgao - description: "Código do órgão atual de lotação ou exercício do servidor." - - - name: cod_padrao - description: "Código do padrão do cargo ocupado." - - - name: cod_situacao_funcional - description: "Código representando a situação funcional atual do servidor (ativo, afastado, etc.)." - - - name: cod_uorg_exercicio - description: "Código da unidade organizacional onde o servidor exerce suas atividades." - - - name: cod_upag - description: "Código da Unidade Pagadora responsável pelo pagamento ao servidor." - - - name: cod_orgao_origem - description: "Código do órgão de origem, em caso de movimentação funcional." - - - name: cpf_chefia_imediata - description: "CPF da chefia imediata, higienizado para conter apenas dígitos." - - - name: dt_exercicio_no_orgao - description: "Data em que o servidor iniciou o exercício no órgão atual." - - - name: dt_fim_vale_ar - description: "Data final da vigência do vale de auxílio-refeição." - - - name: dt_ingresso_funcao - description: "Data de ingresso na função atual." - - - name: dt_ocorr_ingresso_orgao - description: "Data da ocorrência de ingresso no órgão atual." - - - name: dt_ocorr_ingresso_serv_publico - description: "Data da ocorrência de ingresso no serviço público." - - - name: email_chefia_imediata - description: "E-mail institucional da chefia imediata, padronizado em minúsculo." - - - name: email_institucional - description: "E-mail institucional do servidor, padronizado em minúsculo." - - - name: email_servidor - description: "E-mail pessoal do servidor, padronizado em minúsculo." - - - name: ident_unica - description: "Identificação única do servidor no sistema." - - - name: matricula_siape - description: "Matrícula do servidor no sistema SIAPE." - - - name: modalidade_pgd - description: "Modalidade de participação no Programa de Gestão e Desempenho (PGD)." - - - name: nome_atividade_funcao - description: "Descrição textual da atividade ou função exercida." - - - name: nome_chefe_uorg - description: "Nome da chefia imediata da unidade organizacional." - - - name: nome_funcao - description: "Nome da função ocupada." - - - name: nome_jornada - description: "Descrição da jornada de trabalho." - - - name: nome_ocorr_ingresso_orgao - description: "Descrição da ocorrência de ingresso no órgão." - - - name: nome_ocorr_ingresso_serv_publico - description: "Descrição da ocorrência de ingresso no serviço público." - - - name: nome_orgao - description: "Nome do órgão onde o servidor está vinculado." - - - name: nome_regime_juridico - description: "Nome do regime jurídico do servidor ( Estatutário, CLT)." - - - name: nome_situacao_funcional - description: "Descrição da situação funcional do servidor." - - - name: nome_uorg_exercicio - description: "Nome da unidade organizacional de exercício." - - - name: nome_upag - description: "Nome da unidade pagadora responsável pelo servidor." - - - name: participa_pgd - description: "Indica se o servidor participa do Programa de Gestão e Desempenho." - - - name: percentual_ts - description: "Percentual de tempo de trabalho remoto (teletrabalho), convertido para valor numérico ( 0.75 representa 75%)." - - - name: sigla_orgao - description: "Sigla do órgão atual." - - - name: sigla_orgao_origem - description: "Sigla do órgão de origem." - - - name: sigla_regime_juridico - description: "Sigla do regime jurídico do servidor." - - - name: sigla_uorg_exercicio - description: "Sigla da unidade de exercício." - - - name: sigla_upag - description: "Sigla da unidade pagadora." - - - name: cpf - description: "CPF do servidor, com apenas dígitos." - - - name: cod_cargo - description: "Código do cargo efetivo ocupado pelo servidor." - - - name: cod_classe - description: "Código da classe funcional do cargo." - - - name: cod_ocorr_aposentadoria - description: "Código da ocorrência de aposentadoria." - - - name: dt_ini_vale_ar - description: "Data de início do benefício de auxílio-refeição." - - - name: dt_ocorr_aposentadoria - description: "Data da ocorrência de aposentadoria." - - - name: nome_cargo - description: "Nome do cargo efetivo." - - - name: nome_classe - description: "Nome da classe funcional do cargo." - - - name: nome_ocorr_aposentadoria - description: "Descrição da ocorrência de aposentadoria." - - - name: sigla_nivel_cargo - description: "Sigla do nível do cargo ocupado." - - - name: tipo_vale_ar - description: "Tipo de auxílio-refeição concedido." - - - name: cod_ocorr_isencao_ir - description: "Código da ocorrência de isenção de imposto de renda." - - - name: dt_ini_ocorr_isencao_ir - description: "Data de início da ocorrência de isenção de IR." - - - name: nome_ocorr_isencao_ir - description: "Descrição da ocorrência de isenção de imposto de renda." - - - name: cod_uorg_lotacao - description: "Código da unidade de lotação do servidor." - - - name: nome_uorg_lotacao - description: "Nome da unidade de lotação do servidor." - - - name: sigla_uorg_lotacao - description: "Sigla da unidade de lotação do servidor." - - - name: dt_fim_ocorr_isencao_ir - description: "Data de término da isenção de IR." - - - name: cod_ocorr_exclusao - description: "Código da ocorrência de exclusão do servidor do sistema." - - - name: dt_ocorr_exclusao - description: "Data da ocorrência de exclusão do servidor." - - - name: nome_ocorr_exclusao - description: "Descrição da ocorrência de exclusão do servidor." - - - name: dt_uorg_lotacao - description: "Data da lotação na unidade organizacional atual." - - - name: cod_vale_transporte - description: "Código do benefício de vale transporte." - - - name: valor_vale_transporte - description: "Valor monetário do vale transporte." - - - name: dt_uorg_exercicio - description: "Data de início do exercício na unidade organizacional atual." - - - name: pontuacao_desempenho - description: > - Pontuação atribuída ao servidor conforme avaliação de desempenho. - Pode conter letras (A, B, C) ou valores numéricos, depende da origem. Mantido como string. - - - - name: dados_pa - description: > - Tabela bronze com dados de pensionistas e alimentados. - meta: - tags: - - bronze - columns: - - name: agencia_beneficiario - description: "Agência bancária do beneficiário." - - name: banco_beneficiario - description: "Banco do beneficiário." - - name: cod_orgao - description: "Código do órgão." - - name: conta_beneficiario - description: "Conta bancária do beneficiário." - - name: cpf_beneficiario - description: "CPF do beneficiário." - - name: matricula_servidor - description: "Matrícula do servidor instituidor da pensão." - - name: nome_beneficiario - description: "Nome do beneficiário." - - name: valor_ultima_pensao - description: "Valor da última pensão." - - name: cpf_servidor - description: "CPF do servidor instituidor da pensão." - - name: cod_vinculo_servidor - description: "Código do vínculo com o servidor." - - name: nome_alimentado - description: "Nome do alimentado." - - name: nome_vinculo_servidor - description: "Nome do vínculo com o servidor." - - - name: dados_pessoais - description: > - Tabela bronze com dados pessoais dos servidores. - meta: - tags: - - bronze - columns: - - name: cod_cor - description: "Código da cor/raça." - - name: cod_estado_civil - description: "Código do estado civil." - - name: cod_nacionalidade - description: "Código da nacionalidade." - - name: cod_sexo - description: "Código do sexo." - - name: dt_nascimento - description: "Data de nascimento." - - name: grupo_sanguineo - description: "Grupo sanguíneo." - - name: nome_pessoa - description: "Nome da pessoa." - - name: nome_cor - description: "Nome da cor/raça." - - name: nome_estado_civil - description: "Nome do estado civil." - - name: nome_mae - description: "Nome da mãe." - - name: nome_municipio_nascimento - description: "Nome do município de nascimento." - - name: nome_nacionalidade - description: "Nome da nacionalidade." - - name: nome_pai - description: "Nome do pai." - - name: nome_sexo - description: "Nome do sexo." - - name: num_pispasep - description: "Número do PIS/PASEP." - - name: uf_nascimento - description: "UF de nascimento." - - name: cpf - description: "CPF do servidor." - - name: cod_deficiencia_fisica - description: "Código de deficiência física." - - name: nome_deficiencia_fisica - description: "Nome da deficiência física." - - name: dt_chegada_brasil - description: "Data de chegada ao Brasil." - - name: nome_pais_origem - description: "Nome do país de origem." - - - name: dados_uorg - description: > - Tabela bronze com dados das Unidades Organizacionais (UORGs). - meta: - tags: - - bronze - columns: - - name: bairro_uorg - description: "Bairro da UORG." - - name: cep_uorg - description: "CEP da UORG." - - name: codigo_matricula - description: "Código de matrícula." - - name: codigo_municipio_uorg - description: "Código do município da UORG." - - name: codigo_orgao - description: "Código do órgão." - - name: codigo_orgao_uorg - description: "Código da UORG no órgão." - - name: email_uorg - description: "Email da UORG." - - name: tipo_endereco_uorg - description: "Tipo de endereço da UORG." - - name: logradouro_uorg - description: "Logradouro da UORG." - - name: nome_municipio_uorg - description: "Nome do município da UORG." - - name: nome_uorg - description: "Nome da UORG." - - name: telefone_uorg - description: "Telefone da UORG." - - name: numero_endereco_uorg - description: "Número do endereço da UORG." - - name: sigla_uorg - description: "Sigla da UORG." - - name: uf_uorg - description: "UF da UORG." - - name: cpf - description: "CPF associado à UORG." - - name: complemento_endereco_uorg - description: "Complemento do endereço da UORG." - - name: fax_uorg - description: "Fax da UORG." - - - - name: estrutura_organizacional_cargos - description: > - Tabela gold contendo a estrutura organizacional de unidades com informações expandidas de cargos e instâncias. - Os dados foram transformados para extrair e normalizar os campos aninhados no JSON armazenado na coluna `cargos`. - A tabela inclui apenas uma instância por código, priorizando a de maior ordem de grandeza. - meta: - tags: - - bronze - columns: - - name: codigounidade - description: Código da unidade organizacional. - - name: nomeunidade - description: Nome completo da unidade organizacional. - - name: siglaunidade - description: Sigla da unidade organizacional. - - name: municipio - description: Município onde a unidade está localizada. - - name: uf - description: Unidade federativa (UF) correspondente ao município. - - name: denominacao - description: Denominação do cargo. - - name: funcao - description: Função associada à denominação do cargo. - - name: codigo_instancia - description: Código da instância do cargo na estrutura. - - name: nome_titular - description: Nome do servidor titular do cargo. - - name: cpf_titular - description: CPF do servidor titular do cargo (com apenas dígitos numéricos). - - name: ordem_grandeza - description: Nível hierárquico da unidade na estrutura, utilizado para escolher a instância mais relevante. - - - - name: lista_servidores - description: > - Tabela bronze com a lista de servidores e suas UORGs. - meta: - tags: - - bronze - columns: - - name: cod_uorg - description: "Código da UORG." - - name: dt_ultima_transacao - description: "Data da última transação." - - name: cpf - description: "CPF do servidor." - - - name: lista_uorgs - description: > - Tabela bronze com a lista de UORGs. - meta: - tags: - - bronze - columns: - - name: codigo - description: "Código da UORG." - - name: dt_ultima_transacao - description: "Data da última transação." - - name: nome - description: "Nome da UORG." - - - - name: terceirizados - description: > - Tabela gold contendo informações de trabalhadores terceirizados oriundos do sistema ComprasGov. - Inclui dados relacionados ao contrato, função, jornada, remuneração e benefícios, com tratamento de campos textuais e numéricos para padronização. - meta: - tags: - - bronze - columns: - - name: id - description: Identificador único do registro do trabalhador terceirizado. - - name: contrato_id - description: Identificador do contrato ao qual o trabalhador está vinculado. - - name: cpf - description: CPF do trabalhador, extraído da string `usuario`. - - name: nome - description: Nome do trabalhador, extraído da string `usuario`. - - name: funcao_id - description: Identificador da função exercida pelo trabalhador. - - name: descricao_complementar - description: Descrição complementar da função ou atividade do trabalhador. - - name: jornada - description: Quantidade de horas de jornada de trabalho semanal do trabalhador (convertido para numérico). - - name: unidade - description: Unidade organizacional onde o trabalhador presta serviço. - - name: salario - description: Valor do salário mensal do trabalhador (convertido para formato numérico padrão). - - name: custo - description: Custo total do trabalhador para a instituição (convertido para formato numérico padrão). - - name: escolaridade_id - description: Identificador do grau de escolaridade do trabalhador. - - name: data_inicio - description: Data de início do contrato do trabalhador, convertida para formato de data. - - name: data_fim - description: Data de término do contrato do trabalhador, convertida para formato de data. - - name: situacao - description: Situação atual do vínculo contratual do trabalhador (ativo, encerrado). - - name: aux_transporte - description: Valor mensal do auxílio transporte recebido pelo trabalhador (convertido para numérico). - - name: vale_alimentacao - description: Valor mensal do vale alimentação recebido pelo trabalhador (convertido para numérico). - - - - name: unidade_organizacional - description: > - Tabela gold que representa a hierarquia das unidades organizacionais extraídas do sistema Siorg. - A hierarquia é construída de forma recursiva, partindo de uma unidade raiz definida manualmente. - A tabela traz dados estruturados sobre as unidades, seus vínculos parentais e o nível de profundidade hierárquica. - meta: - tags: - - bronze - columns: - - name: codigounidade - description: Código da unidade organizacional (sem prefixos de URI). - - name: codigounidadepai - description: Código da unidade organizacional pai, representando a hierarquia entre unidades. - - name: codigoorgaoentidade - description: Código do órgão ou entidade ao qual a unidade pertence. - - name: codigotipounidade - description: Código do tipo da unidade organizacional (departamento, secretaria). - - name: nome - description: Nome completo da unidade organizacional. - - name: sigla - description: Sigla da unidade organizacional. - - name: codigoesfera - description: Código da esfera governamental (federal, estadual, municipal). - - name: codigopoder - description: Código do poder ao qual a unidade está vinculada (Executivo, Judiciário). - - name: codigonaturezajuridica - description: Código da natureza jurídica da unidade. - - name: codigosubnaturezajuridica - description: Código da subnatureza jurídica da unidade. - - name: nivelnormatizacao - description: Nível de normatização da unidade organizacional. - - name: versaoconsulta - description: Número da versão da consulta no momento da extração dos dados. - - name: datafinalversaoconsulta - description: Data de finalização da versão da consulta. - - name: operacao - description: Tipo de operação registrada (inclusão, alteração, exclusão). - - name: codigounidadepaianterior - description: Código da unidade pai anterior (em caso de alteração estrutural). - - name: codigoorgaoentidadeanterior - description: Código do órgão/entidade anterior da unidade (em caso de mudança). - - name: ordem_grandeza - description: Nível hierárquico da unidade dentro da estrutura organizacional (quanto maior, mais profunda). - - name: caminho_unidade - description: Caminho hierárquico concatenado das siglas das unidades até o nível atual. - - - # Silver - - name: afastamento_consolidado - description: > - Tabela gold que consolida os registros de afastamento dos servidores, integrando as fontes - `dados_afastamento` e `afastamento_historico`, com enriquecimento de informações vindas de - `dados_pessoais` (nome do servidor) e `dados_funcionais` (função e unidade de exercício). - O processo inclui deduplicação inteligente priorizando os registros mais confiáveis, - utilizando lógica de ranking e partição por CPF e data de início do afastamento. - meta: - tags: - - silver - columns: - - name: adiantamento_salario_ferias - description: Indica se houve adiantamento de salário junto às férias no período do afastamento. - - name: ano_exercicio - description: Ano de exercício a que o afastamento se refere. - - name: dt_fim - description: Data de término do afastamento. - - name: dt_fim_aquisicao - description: Data final do período aquisitivo das férias relacionadas ao afastamento. - - name: dt_ini - description: Data de início do afastamento. - - name: dt_inicio_aquisicao - description: Data inicial do período aquisitivo de férias. - - name: dt_inicio_ferias_interrompidas - description: Data de início das férias interrompidas, se houver. - - name: dias_restantes - description: Quantidade de dias restantes de férias ou afastamento. - - name: gratificacao_natalina - description: Indica se houve gratificação natalina no período do afastamento. - - name: numero_parcela - description: Número da parcela relacionada ao afastamento. - - name: parcela_continuacao_interrupcao - description: Indica se o afastamento é continuação ou interrupção de uma parcela anterior. - - name: parcela_interrompida - description: Indica se a parcela foi interrompida. - - name: qtde_dias - description: Quantidade total de dias do afastamento. - - name: cpf - description: CPF do servidor. - - name: cod_diploma_afastamento - description: Código do diploma legal que ampara o afastamento. - - name: cod_ocorrencia - description: Código da ocorrência de afastamento. - - name: dt_publicacao_afastamento - description: Data de publicação do afastamento no diário oficial ou sistema correspondente. - - name: desc_diploma_afastamento - description: Descrição textual do diploma legal de afastamento. - - name: desc_ocorrencia - description: Descrição da ocorrência relacionada ao afastamento. - - name: numero_diploma_afastamento - description: Número do diploma legal do afastamento. - - name: gr_matricula - description: Número da matrícula do servidor no sistema GRH, quando disponível. - - name: origem_dados - description: Origem do dado de afastamento (`dados_afastamento` ou `afastamento_historico`). - - name: nome_pessoa - description: Nome completo do servidor, obtido a partir da tabela de dados pessoais. - - name: cod_funcao - description: Código da função de chefia ou cargo de confiança ocupado pelo servidor no momento do afastamento. - - name: sigla_uorg_exercicio - description: Sigla da unidade organizacional onde o servidor exercia suas funções. - - - - - name: quantitativo_alocados_ocupados - description: > - Tabela gold que consolida e compara os quantitativos de cargos ocupados e cargos previstos - em funções de chefia, correlacionando os dados dos sistemas SIAPE e SIORG. A lógica aplica - uma combinação heurística de códigos de função com base na estrutura dos códigos e siglas - das unidades organizacionais, com objetivo de identificar inconsistências, sobras ou - vacâncias entre as vagas disponíveis e as efetivamente ocupadas. - meta: - tags: - - silver - columns: - - name: codigo_siape - description: Código da função do servidor registrado no SIAPE (`cod_funcao`), representando funções ocupadas. - - name: codigo_siorg - description: Código da função de chefia conforme registrado no SIORG (`funcao`), representando vagas previstas. - - name: nomeunidade - description: Nome da unidade organizacional, obtido da base SIORG ou SIAPE (com prioridade para SIORG). - - name: siglaunidade - description: Sigla da unidade organizacional, unificada a partir das duas fontes (com prioridade para SIORG). - - name: nome_cargo - description: Nome ou denominação do cargo/função de chefia, com base em SIORG ou SIAPE. - - name: qtd_vagas_cargo - description: Quantidade de vagas previstas para o cargo de chefia na estrutura organizacional (dados do SIORG). - - name: qtd_vagas_ocupadas - description: Quantidade de cargos de chefia efetivamente ocupados, com base nos registros de servidores no SIAPE. - - name: qtd_cargos_vagos - description: > - Diferença entre o número de vagas previstas (`qtd_vagas_cargo`) e as ocupadas (`qtd_vagas_ocupadas`). - Indica o total de cargos vagos. Retorna `null` se a quantidade de vagas previstas não estiver disponível. - - - - name: servidores_detalhados - description: > - Tabela gold que consolida dados pessoais, funcionais, educacionais e organizacionais dos servidores. - Integra informações de múltiplas fontes do SIAPE, combinando registros de identificação, vínculo funcional, - escolaridade, endereço da unidade de exercício e metadados de transações. Essa visão é útil para análises - completas do perfil dos servidores ativos e inativos, permitindo estudos sociodemográficos, funcionais e - institucionais detalhados. - meta: - tags: - - silver - columns: - # Dados pessoais - - name: cpf - description: CPF do servidor, utilizado como chave primária de junção entre as bases. - - name: nome_pessoa - description: Nome completo do servidor. - - name: dt_nascimento - description: Data de nascimento do servidor. - - name: nome_cor - description: Cor/raça autodeclarada do servidor. - - name: nome_estado_civil - description: Estado civil declarado. - - name: nome_mae - description: Nome da mãe do servidor. - - name: nome_pai - description: Nome do pai do servidor. - - name: nome_municipio_nascimento - description: Município de nascimento do servidor. - - name: nome_nacionalidade - description: Nacionalidade declarada. - - name: nome_sexo - description: Sexo/gênero do servidor. - - # Dados funcionais - - name: matricula_siape - description: Matrícula SIAPE do servidor. - - name: nome_funcao - description: Nome da função de chefia ou cargo comissionado exercido, se houver. - - name: cod_funcao - description: Código da função exercida. - - name: nome_cargo - description: Nome do cargo efetivo. - - name: cod_cargo - description: Código do cargo efetivo. - - name: nome_jornada - description: Jornada de trabalho do servidor. - - name: dt_ingresso_funcao - description: Data de ingresso na função atual. - - name: nome_regime_juridico - description: Regime jurídico do vínculo funcional. - - name: nome_situacao_funcional - description: Situação funcional (ativo, cedido, aposentado etc). - - name: participa_pgd - description: Indica se o servidor participa do Programa de Gestão (teletrabalho). - - # Escolaridade e titulação - - name: nome_escolaridade_principal - description: Nível de escolaridade mais elevado do servidor. - - name: nome_titulacao_principal - description: Título acadêmico mais elevado do servidor ( mestrado, doutorado). - - # Unidade organizacional - - name: nome_uorg_exercicio - description: Nome da unidade organizacional onde o servidor exerce atualmente. - - name: sigla_uorg_exercicio - description: Sigla da unidade de exercício. - - name: nome_uorg_lotacao - description: Nome da unidade de lotação original. - - name: sigla_uorg_lotacao - description: Sigla da unidade de lotação. - - name: nome_orgao_funcional - description: Nome do órgão funcional ao qual o servidor está vinculado. - - name: sigla_orgao_funcional - description: Sigla do órgão funcional. - - # Endereço da unidade - - name: logradouro_uorg - description: Nome do logradouro da unidade. - - name: numero_endereco_uorg - description: Número do endereço da unidade. - - name: complemento_endereco_uorg - description: Complemento do endereço. - - name: bairro_uorg - description: Bairro onde a unidade está localizada. - - name: cep_uorg - description: CEP da unidade. - - name: nome_municipio_uorg - description: Município da unidade. - - name: uf_uorg - description: Unidade federativa da unidade. - - # Contatos institucionais - - name: email_institucional - description: Email institucional do servidor. - - name: email_servidor - description: Email alternativo do servidor. - - name: email_chefia_imediata - description: Email da chefia imediata. - - name: telefone_uorg - description: Telefone da unidade de exercício. - - name: fax_uorg - description: Fax da unidade de exercício. - - # Metadados e auditoria - - name: ident_unica_funcional - description: Identificador único funcional, usado para rastreamento interno. - - name: dt_ultima_transacao_servidor - description: Data da última transação registrada no sistema para o servidor. - - # Informações complementares - - name: cod_ocorr_aposentadoria - description: Código da ocorrência de aposentadoria (se houver). - - name: dt_ocorr_aposentadoria - description: Data da aposentadoria. - - name: cod_vale_transporte - description: Código do vale transporte. - - name: valor_vale_transporte - description: Valor mensal do vale transporte recebido. - - name: pontuacao_desempenho - description: Pontuação de desempenho do servidor, se aplicável. - - - - - - name: tabela_correlacao_cargos - description: > - Tabela final que correlaciona cargos entre os sistemas SIAPE e SIORG, utilizando - combinações de códigos e unidades organizacionais. Cada linha representa um servidor - com informações enriquecidas da função e da chefia, além da hierarquia do cargo e - dados pessoais básicos. Essa tabela é útil para analisar discrepâncias, estruturar - a hierarquia funcional e verificar se as funções atribuídas no SIAPE estão - corretamente refletidas no SIORG. - - meta: - tags: - - silver - columns: - - name: codigo_siape - description: Código da função no SIAPE. - - name: codigo_siorg - description: Código da função no SIORG. - - name: codigo_combinacao_siape - description: Código combinado utilizado para comparar cargos no SIAPE. - - name: codigo_combinacao_siorg - description: Código combinado utilizado para comparar cargos no SIORG. - - name: matricula_siape - description: Matrícula do servidor no sistema SIAPE. - - name: cpf - description: CPF do servidor titular do cargo. - - name: cpf_chefia_imediata - description: CPF da chefia imediata do servidor. - - name: cod_situacao_funcional - description: Código da situação funcional do servidor no SIAPE. - - name: nome_situacao_funcional - description: Descrição da situação funcional. - - name: hierarquia_cargo - description: Valor numérico que representa a posição hierárquica do cargo. Quanto menor, mais alto o cargo. - - name: servidor - description: Nome do servidor titular do cargo. - - name: dt_nascimento - description: Data de nascimento do servidor. - - name: nome_sexo - description: Sexo do servidor. - - name: nome_estado_civil - description: Estado civil do servidor. - - name: nome_nacionalidade - description: Nacionalidade do servidor. - - name: nome_cor - description: Cor/raça autodeclarada pelo servidor. - - name: uf_nascimento - description: Unidade Federativa (UF) de nascimento. - - name: nome_municipio_nascimento - description: Município de nascimento do servidor. - - name: nome_chefia - description: Nome da chefia imediata, obtido pelo CPF da chefia. - - name: codigounidade - description: Código da unidade organizacional onde o cargo está lotado. - - name: codigounidadepai - description: Código da unidade organizacional imediatamente superior. - - name: caminho_unidade - description: Caminho hierárquico da unidade organizacional, representando sua posição na estrutura. - - name: ordem_grandeza - description: Nível de profundidade da unidade na hierarquia institucional. - - name: nomeunidade - description: Nome da unidade organizacional. - - name: siglaunidade - description: Sigla da unidade organizacional. - - name: nome_cargo - description: Denominação do cargo ocupado, conforme SIAPE ou SIORG. - - name: servidores_carreira - description: Classificação se o cargo é de carreira ou nomeação livre. - - - - name: unidades_organizacionais_siorg_siape - description: > - Tabela que correlaciona as unidades organizacionais do SIAPE e do SIORG, - com base na correspondência entre os códigos e siglas das unidades. - Essa tabela é utilizada para identificar quais unidades estão presentes - em ambos os sistemas, apenas no SIAPE ou apenas no SIORG. - - meta: - tags: - - silver - columns: - - name: nome_unidade - description: Nome da unidade organizacional, proveniente do SIAPE ou SIORG. - - name: sigla_uorg - description: Sigla da unidade organizacional. Pode vir do SIAPE (dados_uorg) ou do SIORG (unidade_organizacional). - - name: codigo_unidade_siape - description: Código da unidade organizacional segundo o SIAPE (extraído de dados_uorg). - - name: codigo_unidade_siorg - description: Código da unidade organizacional segundo o SIORG (coluna codigounidade). - - name: tipo_correlacao - description: > - Tipo de correspondência encontrada entre os sistemas: - - "ambos": unidade presente no SIAPE e SIORG. - - "apenas_siorg": presente apenas no SIORG. - - "apenas_siape": presente apenas no SIAPE. - - - # Gold - - name: aposentadorias_resumo - description: > - Tabela de resumo dos servidores aposentados, contendo informações detalhadas - sobre a data de ingresso no serviço público, data de aposentadoria e tempo - de serviço público calculado em anos, meses e dias. A granularidade da tabela - é por CPF, ou seja, cada linha representa um servidor aposentado único. - - meta: - tags: - - gold - columns: - - name: cpf - description: Número do CPF do servidor aposentado. - - name: nome_pessoa - description: Nome completo do servidor aposentado. - - name: dt_ocorr_ingresso_serv_publico - description: Data de ingresso do servidor no serviço público. - - name: dt_ocorr_aposentadoria - description: Data de aposentadoria do servidor. - - name: mes_aposentadoria - description: Mês da aposentadoria (arredondado para o primeiro dia do mês). - - name: nome_situacao_funcional - description: Situação funcional do servidor no momento da aposentadoria. - - name: nome_ocorr_aposentadoria - description: Tipo de ocorrência que levou à aposentadoria do servidor. - - name: nome_cargo - description: Cargo ocupado pelo servidor no momento da aposentadoria. - - name: sigla_nivel_cargo - description: Sigla do nível do cargo ocupado. - - name: classe_padrao - description: Classe e padrão do cargo, no formato "classe-padrão". - - name: age - description: Diferença completa entre as datas de ingresso e aposentadoria, em formato de intervalo. - - name: diff_anos - description: Quantidade de anos entre o ingresso no serviço público e a aposentadoria. - - name: diff_meses - description: Quantidade de meses entre o ingresso e a aposentadoria (ignora anos completos). - - name: diff_dias - description: Quantidade de dias entre o ingresso e a aposentadoria (ignora anos e meses completos). - - - - - name: cargos_consolidado - description: > - Tabela que consolida informações de cargos ocupados e vagos no serviço público, - a partir da junção entre os dados do SIAPE (servidores ativos e detalhados) e - os dados do SIORG (estrutura de cargos disponíveis). Essa junção é feita com - base no CPF do titular do cargo. Quando os campos do SIAPE estão nulos, o cargo - provavelmente está vago. A tabela pode ser utilizada para identificar cargos - vagos por unidade organizacional. - - meta: - tags: - - gold - columns: - - name: siorg_cod_unidade - description: Código da unidade organizacional no SIORG. - - name: siorg_nome_unidade - description: Nome da unidade organizacional no SIORG. - - name: siorg_sigla_unidade - description: Sigla da unidade organizacional no SIORG. - - name: siorg_municipio_unidade - description: Município da unidade organizacional no SIORG. - - name: siorg_uf_unidade - description: Unidade federativa (UF) da unidade organizacional no SIORG. - - name: siorg_denominacao_cargo - description: Denominação do cargo conforme registrado no SIORG. - - name: siorg_funcao - description: Função comissionada associada ao cargo no SIORG. - - name: siorg_cod_instancia_cargo - description: Código da instância do cargo no SIORG. - - name: siorg_cpf_titular - description: CPF do titular do cargo segundo o SIORG. - - name: siorg_nome_titular - description: Nome do titular do cargo segundo o SIORG. - - - name: siape_cpf - description: CPF do servidor conforme os dados do SIAPE. - - name: siape_nome_pessoa - description: Nome completo do servidor no SIAPE. - - name: siape_nome_cargo_efetivo - description: Nome do cargo efetivo ocupado pelo servidor no SIAPE. - - name: siape_nome_funcao_comissionada - description: Nome da função comissionada ocupada pelo servidor no SIAPE. - - name: siape_cod_uorg - description: Código da unidade de exercício do servidor no SIAPE. - - name: siape_nome_uorg - description: Nome da unidade de exercício do servidor no SIAPE. - - name: siape_sigla_uorg - description: Sigla da unidade de exercício do servidor no SIAPE. - - name: siape_uf_uorg - description: Unidade federativa (UF) da unidade de exercício no SIAPE. - - name: siape_situacao_funcional - description: Situação funcional atual do servidor segundo o SIAPE. - - - - - name: hierarquia - description: > - Tabela que correlaciona dados do SIAPE (dados funcionais e pessoais dos servidores) - com a estrutura organizacional do SIORG, atribuindo uma métrica de hierarquia aos cargos - com base na codificação da função. A hierarquia é determinada por uma fórmula baseada - na categoria e no nível do cargo. A tabela também indica se o cargo é de carreira ou de - nomeação livre, além de consolidar informações sobre o servidor e sua chefia imediata. - - meta: - tags: - - gold - columns: - - name: codigo_siape - description: Código da função no SIAPE. - - name: codigo_siorg - description: Código da função no SIORG, com espaços removidos. - - name: codigo_combinacao_siape - description: Código combinado derivado da função no SIAPE, usado para correlação. - - name: codigo_combinacao_siorg - description: Código combinado derivado da função no SIORG, usado para correlação. - - name: matricula_siape - description: Matrícula funcional do servidor no SIAPE. - - name: cpf - description: CPF do servidor. - - name: cpf_chefia_imediata - description: CPF da chefia imediata do servidor. - - name: cod_situacao_funcional - description: Código da situação funcional do servidor. - - name: nome_situacao_funcional - description: Descrição da situação funcional do servidor. - - name: hierarquia_cargo - description: Indicador numérico da hierarquia do cargo (quanto menor, maior o cargo). - - name: servidor - description: Nome do servidor ocupante do cargo. - - name: dt_nascimento - description: Data de nascimento do servidor. - - name: nome_sexo - description: Sexo do servidor. - - name: nome_estado_civil - description: Estado civil do servidor. - - name: nome_nacionalidade - description: Nacionalidade do servidor. - - name: nome_cor - description: Cor/raça do servidor. - - name: uf_nascimento - description: Unidade federativa de nascimento do servidor. - - name: nome_municipio_nascimento - description: Município de nascimento do servidor. - - name: nome_chefia - description: Nome da chefia imediata do servidor. - - name: codigounidade - description: Código da unidade organizacional associada ao cargo. - - name: codigounidadepai - description: Código da unidade organizacional pai (superior). - - name: caminho_unidade - description: Caminho hierárquico completo da unidade. - - name: ordem_grandeza - description: Nível hierárquico da unidade na estrutura organizacional. - - name: nomeunidade - description: Nome da unidade organizacional. - - name: siglaunidade - description: Sigla da unidade organizacional. - - name: nome_cargo - description: Nome ou denominação do cargo ocupado. - - name: servidores_carreira - description: Indica se o servidor está em cargo de carreira ou em nomeação livre. - - - - name: resumo_quadro_pessoal - description: > - Tabela resumo com a distribuição dos servidores públicos por cargo efetivo, gênero, - situação funcional e unidade federativa da unidade de exercício (UF). A granularidade é agregada, - e cada linha representa uma combinação única desses atributos, com a respectiva contagem de servidores. - - meta: - tags: - - gold - columns: - - name: cargo_efetivo - description: > - Nome do cargo efetivo ocupado pelo servidor. Caso não informado, é preenchido com 'N/A'. - - name: genero - description: > - Gênero do servidor (masculino, feminino ou outro), conforme informado no cadastro pessoal. - Em casos de ausência de informação, é preenchido com 'N/A'. - - name: situacao_funcional - description: > - Situação funcional atual do servidor ( Ativo, Aposentado, Cedido, etc). - Valores ausentes são substituídos por 'N/A'. - - name: localidade_uf - description: > - Unidade Federativa (UF) da unidade organizacional onde o servidor exerce suas funções. - Valores ausentes são preenchidos com 'N/A'. - - name: quantidade_servidores - description: > - Quantidade total de servidores distintos (com base no CPF) que se enquadram na combinação dos atributos anteriores. - - -macros: - - name: create_udfs - description: > - Função que cria as UDFs necessárias para o funcionamento do projeto. - Essa função deve ser chamada no início de cada run para garantir que todas as UDFs estejam disponíveis. - - - name: generate_schema_name - description: > - Função que gera o nome do schema a ser utilizado no projeto. - A função dentro desta macro é built-in do dbt. - - ## UDFS - - name: create_f_parse_dates - description: > - Função que cria a UDF f_parse_dates, que é utilizada para converter texto no formato MÊS(texto)/ANO(numero) em datas. - arguments: - - name: in_text - type: text - description: > - Texto a ser convertido em data. - O texto deve estar no formato MÊS(texto)/ANO(numero). Ex.: FEV/2024 -> 2024-02-01 - - - - - - - - - - From d6a2222b17787bccbc9d0e2eba0ab23efc753431 Mon Sep 17 00:00:00 2001 From: mat054 Date: Mon, 27 Oct 2025 15:49:14 -0300 Subject: [PATCH 171/317] fix(linter): corrigindo erros de formatacao do linter --- .../notas_de_credito_ingest_dag.py | 6 +++--- .../programacao_financeira_ingest_dag.py | 9 +++++---- airflow_lappis/plugins/cliente_postgres.py | 2 +- airflow_lappis/plugins/cliente_ted.py | 20 ++++++++++++------- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/notas_de_credito_ingest_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/notas_de_credito_ingest_dag.py index 4af1e31a..3efb024e 100644 --- a/airflow_lappis/dags/data_ingest/transfere_gov/notas_de_credito_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transfere_gov/notas_de_credito_ingest_dag.py @@ -1,7 +1,5 @@ import logging -import yaml from airflow.decorators import dag, task -from airflow.models import Variable from datetime import datetime, timedelta from postgres_helpers import get_postgres_conn from cliente_postgres import ClientPostgresDB @@ -44,7 +42,9 @@ def fetch_and_store_notas_de_credito() -> None: schema="transfere_gov", ) else: - logging.warning(f"Nenhuma nota de crédito encontrada plano de ação {id_plano_acao}") + logging.warning( + f"Nenhuma nota de crédito encontrada plano de ação {id_plano_acao}" + ) fetch_and_store_notas_de_credito() diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/programacao_financeira_ingest_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/programacao_financeira_ingest_dag.py index b0f40415..789e29f5 100644 --- a/airflow_lappis/dags/data_ingest/transfere_gov/programacao_financeira_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transfere_gov/programacao_financeira_ingest_dag.py @@ -1,7 +1,5 @@ import logging -import yaml from airflow.decorators import dag, task -from airflow.models import Variable from datetime import datetime, timedelta from postgres_helpers import get_postgres_conn from cliente_postgres import ClientPostgresDB @@ -30,7 +28,9 @@ def fetch_and_store_programacao_financeira() -> None: id_planos_acao = db.get_id_planos_acao() for id_plano_acao in id_planos_acao: - programacao_financeira = api.get_programacao_financeira_by_id_plano_acao(id_plano_acao) + programacao_financeira = api.get_programacao_financeira_by_id_plano_acao( + id_plano_acao + ) if programacao_financeira: # Adicionar dt_ingest a cada item for item in programacao_financeira: @@ -45,7 +45,8 @@ def fetch_and_store_programacao_financeira() -> None: ) else: logging.warning( - f"Nenhuma programação financeira encontrada plano de ação {id_plano_acao}" + f"Nenhuma programação financeira encontrada " + f"plano de ação {id_plano_acao}" ) fetch_and_store_programacao_financeira() diff --git a/airflow_lappis/plugins/cliente_postgres.py b/airflow_lappis/plugins/cliente_postgres.py index b07401af..e95c2082 100755 --- a/airflow_lappis/plugins/cliente_postgres.py +++ b/airflow_lappis/plugins/cliente_postgres.py @@ -199,7 +199,7 @@ def get_id_programas(self) -> List[int]: cursor.execute(query) id_programas = [row[0] for row in cursor.fetchall()] return id_programas - + def get_id_planos_acao(self) -> List[int]: """Extrai todos os IDs de planos de ação da tabela de planos de ação.""" query = "SELECT id_plano_acao FROM transfere_gov.planos_acao" diff --git a/airflow_lappis/plugins/cliente_ted.py b/airflow_lappis/plugins/cliente_ted.py index 0247c9af..cb09a0e0 100644 --- a/airflow_lappis/plugins/cliente_ted.py +++ b/airflow_lappis/plugins/cliente_ted.py @@ -101,26 +101,32 @@ def get_notas_de_credito_by_id_plano_acao(self, id_plano_acao: int) -> list | No status, data = self.request( http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER ) - + if status == http.HTTPStatus.OK and isinstance(data, list): logging.info(f"Notas de crédito obtidas para plano de ação {id_plano_acao}") return data else: logging.warning(f"Falha ao buscar notas de crédito - Status: {status}") return None - - def get_programacao_financeira_by_id_plano_acao(self, id_plano_acao: int) -> list | None: + + def get_programacao_financeira_by_id_plano_acao( + self, id_plano_acao: int + ) -> list | None: endpoint = f"programacao_financeira?id_plano_acao=eq.{id_plano_acao}" - logging.info(f"Buscando programação financeira pelo plano de ação: {id_plano_acao}") + logging.info( + f"Buscando programação financeira pelo plano de ação: {id_plano_acao}" + ) status, data = self.request( http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER ) - + if status == http.HTTPStatus.OK and isinstance(data, list): - logging.info(f"Programação financeira obtidas para plano de ação {id_plano_acao}") + logging.info( + f"Programação financeira obtidas para plano de ação {id_plano_acao}" + ) return data else: logging.warning(f"Falha ao buscar programação financeira - Status: {status}") - return None \ No newline at end of file + return None From 16ea34d0b8fd48071e2f3960b8a59ee36a4b0ec0 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Wed, 19 Nov 2025 08:37:54 -0300 Subject: [PATCH 172/317] feat(airflow): ingestao de programas e plano de acao transferegov Co-authored-by: Mateus Castro --- .../planos_acao_especiais_ingest_dag.py | 74 +++++++ .../programas_especiais_ingest_dag.py | 66 +++++++ .../plugins/cliente_transfere_gov.py | 187 ++++++++++++++++++ 3 files changed, 327 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/transfere_gov/planos_acao_especiais_ingest_dag.py create mode 100644 airflow_lappis/dags/data_ingest/transfere_gov/programas_especiais_ingest_dag.py create mode 100644 airflow_lappis/plugins/cliente_transfere_gov.py diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/planos_acao_especiais_ingest_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/planos_acao_especiais_ingest_dag.py new file mode 100644 index 00000000..572add26 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/transfere_gov/planos_acao_especiais_ingest_dag.py @@ -0,0 +1,74 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from postgres_helpers import get_postgres_conn +from cliente_transfere_gov import ClienteTransfereGov +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Davi e Mateus", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["transfere_gov_api", "planos_acao_especiais"], +) +def api_planos_acao_especiais_dag() -> None: + """DAG para buscar e armazenar planos de ação especiais do Transfere Gov.""" + + @task + def fetch_and_store_planos_acao_especiais() -> None: + logging.info( + "[planos_acao_especiais_ingest_dag.py] Iniciando extração de " + "planos de ação especiais" + ) + + api = ClienteTransfereGov() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + # Buscar IDs dos programas especiais + query = "SELECT DISTINCT id_programa FROM transfere_gov.programas_especiais" + programas_ids = db.execute_query(query) + + if not programas_ids: + logging.warning( + "[planos_acao_especiais_ingest_dag.py] Nenhum programa encontrado" + ) + return + + total_planos = 0 + for (id_programa,) in programas_ids: + logging.info( + f"[planos_acao_especiais_ingest_dag.py] Buscando planos de ação " + f"para programa {id_programa}" + ) + + planos_data = api.get_all_planos_acao_especiais_by_programa(id_programa) + + if planos_data: + for plano in planos_data: + plano["dt_ingest"] = datetime.now().isoformat() + + db.insert_data( + planos_data, + "planos_acao_especiais", + conflict_fields=["id_plano_acao"], + primary_key=["id_plano_acao"], + schema="transfere_gov", + ) + total_planos += len(planos_data) + + logging.info( + f"[planos_acao_especiais_ingest_dag.py] Concluído. " + f"Total: {total_planos} planos de ação inseridos/atualizados" + ) + + fetch_and_store_planos_acao_especiais() + + +dag_instance = api_planos_acao_especiais_dag() diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/programas_especiais_ingest_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/programas_especiais_ingest_dag.py new file mode 100644 index 00000000..ed2f8874 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/transfere_gov/programas_especiais_ingest_dag.py @@ -0,0 +1,66 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from postgres_helpers import get_postgres_conn +from cliente_transfere_gov import ClienteTransfereGov +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Davi e Mateus", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["transfere_gov_api", "programas_especiais"], +) +def api_programas_especiais_dag() -> None: + """DAG para buscar e armazenar programas especiais do Transfere Gov.""" + + @task + def fetch_and_store_programas_especiais() -> None: + logging.info( + "[programas_especiais_ingest_dag.py] Iniciando extração programas especiais" + ) + + api = ClienteTransfereGov() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + # Busca todos os programas especiais com paginação automática + programas_data = api.get_all_programas_especiais(page_size=1000) + + if programas_data and len(programas_data) > 0: + # Adicionar dt_ingest a cada programa + for programa in programas_data: + programa["dt_ingest"] = datetime.now().isoformat() + + # Inserir/atualizar dados no banco + logging.info( + f"[programas_especiais_ingest_dag.py] Inserindo {len(programas_data)} " + "programas especiais no schema transfere_gov" + ) + db.insert_data( + programas_data, + "programas_especiais", + conflict_fields=["id_programa"], + primary_key=["id_programa"], + schema="transfere_gov", + ) + + logging.info( + f"[programas_especiais_ingest_dag.py] Concluído. Total de " + f"{len(programas_data)} programas especiais inseridos/atualizados" + ) + else: + logging.warning( + "[programas_especiais_ingest_dag.py] Nenhum programa especial encontrado" + ) + + fetch_and_store_programas_especiais() + + +dag_instance = api_programas_especiais_dag() diff --git a/airflow_lappis/plugins/cliente_transfere_gov.py b/airflow_lappis/plugins/cliente_transfere_gov.py new file mode 100644 index 00000000..f9fb3e11 --- /dev/null +++ b/airflow_lappis/plugins/cliente_transfere_gov.py @@ -0,0 +1,187 @@ +import http +import logging +from typing import Optional +from cliente_base import ClienteBase + + +class ClienteTransfereGov(ClienteBase): + BASE_URL = "https://api.transferegov.gestao.gov.br/transferenciasespeciais/" + BASE_HEADER = {"accept": "application/json", "User-Agent": "Airflow-GovHub/1.0"} + + def __init__(self) -> None: + super().__init__(base_url=ClienteTransfereGov.BASE_URL) + logging.info( + "[cliente_transfere_gov.py] Initialized ClienteTransfereGov with base_url: " + f"{ClienteTransfereGov.BASE_URL}" + ) + + def get_programas_especiais( + self, limit: int = 1000, offset: int = 0 + ) -> Optional[list]: + """ + Obter programas especiais com paginação. + + Args: + limit (int): Quantidade de registros por página (padrão: 1000) + offset (int): Deslocamento inicial (padrão: 0) + + Returns: + list: lista de programas especiais ou None se falhar + """ + endpoint = "programa_especial" + params = { + "select": "*", + "order": "id_programa.asc", + "limit": limit, + "offset": offset, + } + + logging.info( + f"[cliente_transfere_gov.py] Fetching programas especiais with " + f"limit={limit}, offset={offset}" + ) + + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER, params=params + ) + + if status == http.HTTPStatus.OK and isinstance(data, list): + logging.info( + f"[cliente_transfere_gov.py] Successfully fetched {len(data)} " + "programas especiais" + ) + return data + else: + logging.warning( + f"[cliente_transfere_gov.py] Failed to fetch programas especiais " + f"with status: {status}" + ) + return None + + def get_all_programas_especiais(self, page_size: int = 1000) -> list: + """ + Obter todos os programas especiais com paginação automática. + + Args: + page_size (int): Quantidade de registros por requisição (padrão: 1000) + + Returns: + list: lista completa de programas especiais + """ + all_data = [] + offset = 0 + page = 1 + + logging.info( + "[cliente_transfere_gov.py] Starting full extraction of programas especiais" + ) + + while True: + logging.info( + f"[cliente_transfere_gov.py] Fetching page {page} " f"(offset: {offset})" + ) + + data = self.get_programas_especiais(limit=page_size, offset=offset) + + if not data or len(data) == 0: + logging.info( + "[cliente_transfere_gov.py] No more data received. " + "Extraction complete." + ) + break + + all_data.extend(data) + logging.info( + f"[cliente_transfere_gov.py] Page {page} fetched: {len(data)} records. " + f"Total so far: {len(all_data)}" + ) + + # Se recebemos menos registros que o limite, é a última página + if len(data) < page_size: + logging.info("[cliente_transfere_gov.py] Last page reached.") + break + + offset += page_size + page += 1 + + logging.info( + f"[cliente_transfere_gov.py] Extraction completed. " + f"Total records: {len(all_data)}" + ) + return all_data + + def get_planos_acao_especiais_by_programa( + self, id_programa: int, limit: int = 1000, offset: int = 0 + ) -> Optional[list]: + """ + Obter planos de ação especiais por ID do programa com paginação. + + Args: + id_programa (int): ID do programa + limit (int): Quantidade de registros por página (padrão: 1000) + offset (int): Deslocamento inicial (padrão: 0) + + Returns: + list: lista de planos de ação especiais ou None se falhar + """ + endpoint = f"plano_acao_especial?id_programa=eq.{id_programa}" + params = {"select": "*", "limit": limit, "offset": offset} + + logging.info( + f"[cliente_transfere_gov.py] Fetching planos de ação especiais for " + f"id_programa={id_programa}, limit={limit}, offset={offset}" + ) + + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER, params=params + ) + + if status == http.HTTPStatus.OK and isinstance(data, list): + logging.info( + f"[cliente_transfere_gov.py] Successfully fetched {len(data)} " + f"planos de ação for programa {id_programa}" + ) + return data + else: + logging.warning( + f"[cliente_transfere_gov.py] Failed to fetch planos de ação for " + f"programa {id_programa} with status: {status}" + ) + return None + + def get_all_planos_acao_especiais_by_programa( + self, id_programa: int, page_size: int = 1000 + ) -> list: + """ + Obter todos os planos de ação especiais de um programa com paginação automática. + + Args: + id_programa (int): ID do programa + page_size (int): Quantidade de registros por requisição (padrão: 1000) + + Returns: + list: lista completa de planos de ação especiais + """ + all_data = [] + offset = 0 + + while True: + data = self.get_planos_acao_especiais_by_programa( + id_programa, limit=page_size, offset=offset + ) + + if not data or len(data) == 0: + break + + all_data.extend(data) + + if len(data) < page_size: + break + + offset += page_size + + logging.info( + f"[cliente_transfere_gov.py] Total planos de ação for programa " + f"{id_programa}: {len(all_data)}" + ) + return all_data From 318cb3f6d1e7fc7f1ee16b5fe888b4625cdd77c4 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Wed, 19 Nov 2025 09:03:59 -0300 Subject: [PATCH 173/317] =?UTF-8?q?feat(dags):=20renomeia=20pasta=20do=20t?= =?UTF-8?q?ransferegov=20e=20esquema=20de=20inser=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../planos_acao_especiais_ingest_dag.py | 4 ++-- .../programas_especiais_ingest_dag.py | 4 ++-- ...iente_transfere_gov.py => cliente_transferegov_emendas.py} | 0 3 files changed, 4 insertions(+), 4 deletions(-) rename airflow_lappis/dags/data_ingest/{transfere_gov => transfere_gov_emendas}/planos_acao_especiais_ingest_dag.py (95%) rename airflow_lappis/dags/data_ingest/{transfere_gov => transfere_gov_emendas}/programas_especiais_ingest_dag.py (95%) rename airflow_lappis/plugins/{cliente_transfere_gov.py => cliente_transferegov_emendas.py} (100%) diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/planos_acao_especiais_ingest_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov_emendas/planos_acao_especiais_ingest_dag.py similarity index 95% rename from airflow_lappis/dags/data_ingest/transfere_gov/planos_acao_especiais_ingest_dag.py rename to airflow_lappis/dags/data_ingest/transfere_gov_emendas/planos_acao_especiais_ingest_dag.py index 572add26..e040b600 100644 --- a/airflow_lappis/dags/data_ingest/transfere_gov/planos_acao_especiais_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transfere_gov_emendas/planos_acao_especiais_ingest_dag.py @@ -2,7 +2,7 @@ from airflow.decorators import dag, task from datetime import datetime, timedelta from postgres_helpers import get_postgres_conn -from cliente_transfere_gov import ClienteTransfereGov +from cliente_transferegov_emendas import ClienteTransfereGov from cliente_postgres import ClientPostgresDB @@ -59,7 +59,7 @@ def fetch_and_store_planos_acao_especiais() -> None: "planos_acao_especiais", conflict_fields=["id_plano_acao"], primary_key=["id_plano_acao"], - schema="transfere_gov", + schema="transfere_gov_emendas", ) total_planos += len(planos_data) diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/programas_especiais_ingest_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov_emendas/programas_especiais_ingest_dag.py similarity index 95% rename from airflow_lappis/dags/data_ingest/transfere_gov/programas_especiais_ingest_dag.py rename to airflow_lappis/dags/data_ingest/transfere_gov_emendas/programas_especiais_ingest_dag.py index ed2f8874..243140eb 100644 --- a/airflow_lappis/dags/data_ingest/transfere_gov/programas_especiais_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transfere_gov_emendas/programas_especiais_ingest_dag.py @@ -2,7 +2,7 @@ from airflow.decorators import dag, task from datetime import datetime, timedelta from postgres_helpers import get_postgres_conn -from cliente_transfere_gov import ClienteTransfereGov +from cliente_transferegov_emendas import ClienteTransfereGov from cliente_postgres import ClientPostgresDB @@ -48,7 +48,7 @@ def fetch_and_store_programas_especiais() -> None: "programas_especiais", conflict_fields=["id_programa"], primary_key=["id_programa"], - schema="transfere_gov", + schema="transfere_gov_emendas", ) logging.info( diff --git a/airflow_lappis/plugins/cliente_transfere_gov.py b/airflow_lappis/plugins/cliente_transferegov_emendas.py similarity index 100% rename from airflow_lappis/plugins/cliente_transfere_gov.py rename to airflow_lappis/plugins/cliente_transferegov_emendas.py From 0bf2509661f1bfee81b107ec72ac8d2bd0f7414b Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Wed, 19 Nov 2025 09:11:01 -0300 Subject: [PATCH 174/317] feat(dags): padroniza transferegov --- .../planos_acao_especiais_ingest_dag.py | 2 +- .../programas_especiais_ingest_dag.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename airflow_lappis/dags/data_ingest/{transfere_gov_emendas => transferegov_emendas}/planos_acao_especiais_ingest_dag.py (97%) rename airflow_lappis/dags/data_ingest/{transfere_gov_emendas => transferegov_emendas}/programas_especiais_ingest_dag.py (97%) diff --git a/airflow_lappis/dags/data_ingest/transfere_gov_emendas/planos_acao_especiais_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/planos_acao_especiais_ingest_dag.py similarity index 97% rename from airflow_lappis/dags/data_ingest/transfere_gov_emendas/planos_acao_especiais_ingest_dag.py rename to airflow_lappis/dags/data_ingest/transferegov_emendas/planos_acao_especiais_ingest_dag.py index e040b600..5a8d1117 100644 --- a/airflow_lappis/dags/data_ingest/transfere_gov_emendas/planos_acao_especiais_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/planos_acao_especiais_ingest_dag.py @@ -59,7 +59,7 @@ def fetch_and_store_planos_acao_especiais() -> None: "planos_acao_especiais", conflict_fields=["id_plano_acao"], primary_key=["id_plano_acao"], - schema="transfere_gov_emendas", + schema="transferegov_emendas", ) total_planos += len(planos_data) diff --git a/airflow_lappis/dags/data_ingest/transfere_gov_emendas/programas_especiais_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/programas_especiais_ingest_dag.py similarity index 97% rename from airflow_lappis/dags/data_ingest/transfere_gov_emendas/programas_especiais_ingest_dag.py rename to airflow_lappis/dags/data_ingest/transferegov_emendas/programas_especiais_ingest_dag.py index 243140eb..dc176e5c 100644 --- a/airflow_lappis/dags/data_ingest/transfere_gov_emendas/programas_especiais_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/programas_especiais_ingest_dag.py @@ -48,7 +48,7 @@ def fetch_and_store_programas_especiais() -> None: "programas_especiais", conflict_fields=["id_programa"], primary_key=["id_programa"], - schema="transfere_gov_emendas", + schema="transferegov_emendas", ) logging.info( From 73d6d5ee03ad217ebac93606b94390a074469e57 Mon Sep 17 00:00:00 2001 From: GabrielaTiago Date: Wed, 19 Nov 2025 12:52:40 -0300 Subject: [PATCH 175/317] feat(docs): adiciona template de mensagem de commit Closes #36 --- .github/TEMPLATES/COMMIT_TEMPLATE.md | 68 ++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 .github/TEMPLATES/COMMIT_TEMPLATE.md diff --git a/.github/TEMPLATES/COMMIT_TEMPLATE.md b/.github/TEMPLATES/COMMIT_TEMPLATE.md new file mode 100644 index 00000000..90f6168a --- /dev/null +++ b/.github/TEMPLATES/COMMIT_TEMPLATE.md @@ -0,0 +1,68 @@ +# Modelo de Mensagem de Commit (Conventional Commits) + +Este documento serve como um guia rápido para a criação de mensagens de commit padronizadas. O uso deste formato é essencial para manter o histórico do projeto legível, facilitar a automação e gerar changelogs de forma automática. + +## Estrutura Principal + +Cada mensagem de commit consiste em um cabeçalho, um corpo opcional e um rodapé opcional. A estrutura é a seguinte: + +```md +[escopo opcional]: + +[corpo opcional] + +[rodapé(s) opcional(is)] +``` + +- **tipo**: Obrigatório. Define a categoria da mudança (ex: `feat`, `fix`, `docs`). +- **escopo**: Opcional. Especifica a parte do código que foi alterada (ex: `api`, `parser`, `database`). +- **descrição**: Obrigatório. Um resumo conciso da mudança, em letras minúsculas e sem ponto final. +- **corpo**: Opcional. Fornece mais contexto, explicando o "porquê" da mudança. Separado da descrição por uma linha em branco. +- **rodapé**: Opcional. Usado para referenciar issues (ex: `Refs: #42`) ou para declarar _breaking changes_ (ex: `BREAKING CHANGE:...`). + +## Tipos de Commit Recomendados + +| Tipo | Descrição | +| :--------- | :----------------------------------------------------------------------------- | +| `feat` | Introduz uma nova funcionalidade ou capacidade. | +| `fix` | Corrige um bug ou erro no código. | +| `docs` | Alterações relacionadas exclusivamente à documentação. | +| `refactor` | Alterações no código que não corrigem um bug nem adicionam uma funcionalidade. | +| `perf` | Uma mudança de código que melhora o desempenho. | +| `test` | Adição ou correção de testes automatizados. | +| `build` | Mudanças que afetam o sistema de build ou dependências externas. | +| `ci` | Mudanças nos arquivos e scripts de configuração de Integração Contínua (CI). | +| `chore` | Outras mudanças que não modificam o código-fonte ou os testes. | +| `style` | Mudanças de estilo de código que não afetam a lógica (formatação, etc.). | + +### Exemplos Práticos + +**1. Commit de correção de bug (fix):** + +`fix: corrige cálculo de offset na paginação da API` + +**2. Commit de nova funcionalidade (feat) com escopo:** + +```md +feat(parser): adiciona suporte para o formato de dados do TSE + +Refs: #45 +``` + +**3. Commit com corpo para mais detalhes:** + +```md +perf(database): otimiza query para busca de metadados + +A query anterior utilizava um JOIN desnecessário que causava lentidão +em datasets com mais de 10.000 registros. Esta mudança simplifica +a consulta e adiciona um índice na coluna de metadados. +``` + +**4. Commit que fecha uma issue do GitHub:** + +```md +fix(ui): resolve problema de renderização de tabelas no Firefox + +Closes: #78 +``` From 697701c58e9d97a0afaa08446116db76ad0427ab Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira <143732704+davi-aguiar-vieira@users.noreply.github.com> Date: Sun, 23 Nov 2025 23:19:58 -0300 Subject: [PATCH 176/317] Feat/cliente GitHub (#43) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat/cliente github * feat(airflow): ingestao de programas e plano de acao transferegov Co-authored-by: Mateus Castro * feat(dags): renomeia pasta do transferegov e esquema de inserção * feat(dags): padroniza transferegov * feat(docs): adiciona template de mensagem de commit Closes #36 * feat:adiciona modelos dbt e atualiza cliente dag * fix/mypy * delete/dashboard-readme * refactor: refatora modelos dbt * feat: dbt seed para estados --------- Co-authored-by: Mateus Castro Co-authored-by: GabrielaTiago --- airflow_lappis/dags/dashboards/__init__.py | 3 + .../dashboards/dashboard_servidores_dag.py | 168 ++++++++++++++++++ .../pessoas_dbt/gold/distribuicao_genero.sql | 8 + .../pessoas_dbt/gold/distribuicao_mapa_uf.sql | 36 ++++ .../gold/distribuicao_raca_cor.sql | 5 + .../gold/distribuicao_situacao_funcional.sql | 32 ++++ .../pessoas_dbt/gold/kpis_servidores.sql | 47 +++++ .../ipea/models/pessoas_dbt/gold/schema.yml | 135 ++++++++++++++ .../gold/tabela_servidores_agregada.sql | 59 ++++++ .../silver/servidores_completos.sql | 43 +++++ .../dags/dbt/ipea/seeds/estados_brasil.csv | 28 +++ airflow_lappis/plugins/cliente_github.py | 137 ++++++++++++++ airflow_lappis/plugins/cliente_postgres.py | 146 +++++++++++++++ docker-compose.yml | 2 +- 14 files changed, 848 insertions(+), 1 deletion(-) create mode 100644 airflow_lappis/dags/dashboards/__init__.py create mode 100644 airflow_lappis/dags/dashboards/dashboard_servidores_dag.py create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_genero.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_mapa_uf.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_raca_cor.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_situacao_funcional.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/kpis_servidores.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/tabela_servidores_agregada.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/servidores_completos.sql create mode 100644 airflow_lappis/dags/dbt/ipea/seeds/estados_brasil.csv create mode 100644 airflow_lappis/plugins/cliente_github.py diff --git a/airflow_lappis/dags/dashboards/__init__.py b/airflow_lappis/dags/dashboards/__init__.py new file mode 100644 index 00000000..da6b0a9a --- /dev/null +++ b/airflow_lappis/dags/dashboards/__init__.py @@ -0,0 +1,3 @@ +""" +Módulo de DAGs para geração de dashboards. +""" diff --git a/airflow_lappis/dags/dashboards/dashboard_servidores_dag.py b/airflow_lappis/dags/dashboards/dashboard_servidores_dag.py new file mode 100644 index 00000000..d79f1578 --- /dev/null +++ b/airflow_lappis/dags/dashboards/dashboard_servidores_dag.py @@ -0,0 +1,168 @@ +""" +DAG para gerar arquivo JSON com dados do dashboard de servidores. +""" + +import json +import logging +from datetime import datetime, timedelta +from typing import Dict + +from airflow.decorators import dag, task +from airflow.models import Variable + +from postgres_helpers import get_postgres_conn +from cliente_postgres import ClientPostgresDB +from cliente_github import ClienteGitHub + +# Configuração de logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# Configurações do GitHub +GITHUB_OWNER = "GovHub-br" +GITHUB_REPO = "gov-hub" +GITHUB_FILE_PATH = "docs/land/public/data/pessoas_visao_geral.json" +GITHUB_BRANCH = "main" + + +@dag( + dag_id="dashboard_servidores_json", + schedule_interval="0 6 * * *", # Executa diariamente às 6h + start_date=datetime(2025, 11, 16), + catchup=False, + default_args={ + "owner": "Davi", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["dashboard", "pessoas", "json"], + description="Gera arquivo JSON com dados do dashboard de servidores", +) +def dashboard_servidores_dag() -> None: + """ + DAG que gera arquivo JSON consolidado com dados do dashboard de servidores. + """ + + @task + def generate_dashboard_json() -> Dict: + """ + Gera dados do dashboard de servidores. + + Returns: + Dicionário com os dados do dashboard + """ + logger.info("Iniciando geração dos dados do dashboard") + + try: + # Conectar ao banco de dados usando helper + postgres_conn_str = get_postgres_conn() + client = ClientPostgresDB(postgres_conn_str) + logger.info("Conectado ao banco de dados com sucesso") + + # Buscar dados + logger.info("Buscando KPIs...") + kpis = client.get_dashboard_kpis() + + logger.info("Buscando distribuição por gênero...") + genero = client.get_dashboard_genero() + + logger.info("Buscando distribuição por raça/cor...") + raca_cor = client.get_dashboard_raca_cor() + + logger.info("Buscando distribuição por situação funcional...") + situacao_funcional = client.get_dashboard_situacao_funcional() + + logger.info("Buscando distribuição geográfica por UF...") + mapa_uf = client.get_dashboard_mapa_uf() + + logger.info("Buscando tabela de servidores agregada...") + tabela_servidores = client.get_dashboard_tabela_servidores(limit=100) + + # Montar estrutura do JSON + dashboard_data = { + "meta": {"atualizado_em": datetime.now().isoformat() + "Z"}, + "kpis": { + "total_servidores": kpis.get("total_servidores", 0), + "servidores_ativos_permanentes": kpis.get( + "servidores_ativos_permanentes", 0 + ), + "aposentados": kpis.get("aposentados", 0), + "estagiarios": kpis.get("estagiarios", 0), + "terceirizados": kpis.get("terceirizados", 0), + }, + "genero": genero, + "raca_cor": raca_cor, + "mapa_uf": mapa_uf, + "situacao_funcional": situacao_funcional, + "tabela_servidores": tabela_servidores, + } + + return dashboard_data + + except Exception as e: + logger.error(f"Erro ao gerar dados do dashboard: {str(e)}") + raise + + @task + def publish_to_github(dashboard_data: Dict) -> Dict[str, str]: + """ + Publica os dados do dashboard no repositório GitHub. + + Args: + dashboard_data: Dicionário com os dados do dashboard + + Returns: + Informações sobre o commit realizado + """ + logger.info("Iniciando publicação dos dados no GitHub") + + try: + # Obter token do GitHub das variáveis do Airflow + github_token = Variable.get("GITHUB_TOKEN") + logger.info("Token do GitHub obtido com sucesso") + + # Converter dicionário para JSON string + json_content = json.dumps(dashboard_data, ensure_ascii=False, indent=2) + logger.info("Dados convertidos para JSON") + + # Inicializar cliente GitHub + github_client = ClienteGitHub(github_token) + + # Criar mensagem de commit + commit_message = ( + f"Update dashboard data - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" + ) + + # Publicar no GitHub + logger.info(f"Publicando em {GITHUB_OWNER}/{GITHUB_REPO}:{GITHUB_FILE_PATH}") + result = github_client.update_file( + owner=GITHUB_OWNER, + repo=GITHUB_REPO, + path=GITHUB_FILE_PATH, + content=json_content, + message=commit_message, + branch=GITHUB_BRANCH, + ) + + commit_info = { + "commit_sha": result.get("commit", {}).get("sha", ""), + "commit_url": result.get("commit", {}).get("html_url", ""), + "file_url": result.get("content", {}).get("html_url", ""), + } + + logger.info("Arquivo publicado com sucesso no GitHub!") + logger.info(f"Commit SHA: {commit_info['commit_sha']}") + logger.info(f"URL do arquivo: {commit_info['file_url']}") + + return commit_info + + except Exception as e: + logger.error(f"Erro ao publicar no GitHub: {str(e)}") + raise + + # Definir dependências entre tasks usando XCom + dashboard_data = generate_dashboard_json() + publish_to_github(dashboard_data) + + +dag_instance = dashboard_servidores_dag() diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_genero.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_genero.sql new file mode 100644 index 00000000..da8d756f --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_genero.sql @@ -0,0 +1,8 @@ +-- Distribuição de servidores por gênero +select + nome_sexo as genero, + count(*) as quantidade_servidores, + count(*) * 1.0 / sum(count(*)) over () as percentual_distribuicao +from {{ ref("servidores_completos") }} +group by nome_sexo +order by percentual_distribuicao desc diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_mapa_uf.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_mapa_uf.sql new file mode 100644 index 00000000..71d75e21 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_mapa_uf.sql @@ -0,0 +1,36 @@ +-- Modelo para gerar a distribuição geográfica de servidores por UF +-- Retorna todos os estados brasileiros com suas respectivas contagens e percentuais +with + -- Obter todos os servidores com localização + servidores_localizacao as ( + select distinct + df.cpf, du.uf_uorg, du.nome_municipio_uorg, df.nome_situacao_funcional + from {{ ref("dados_funcionais") }} df + inner join {{ ref("dados_uorg") }} du on df.sigla_uorg_exercicio = du.sigla_uorg + where du.uf_uorg is not null + ), + + -- Contar servidores por UF + contagem_por_uf as ( + select uf_uorg, count(distinct cpf) as valor + from servidores_localizacao + group by uf_uorg + ), + + -- Calcular totais para percentual + total_servidores as (select sum(valor) as total from contagem_por_uf) + +-- Juntar todos os estados com suas contagens (0 para estados sem servidores) +select + eb.sigla_uf, + eb.nome_uf, + coalesce(cpu.valor, 0) as valor, + case + when coalesce(cpu.valor, 0) = 0 + then '0%' + else concat(round((coalesce(cpu.valor, 0) * 100.0 / ts.total), 0), '%') + end as percentual +from {{ ref("estados_brasil") }} eb +cross join total_servidores ts +left join contagem_por_uf cpu on eb.sigla_uf = cpu.uf_uorg +order by eb.sigla_uf diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_raca_cor.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_raca_cor.sql new file mode 100644 index 00000000..67eb34d6 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_raca_cor.sql @@ -0,0 +1,5 @@ +-- Distribuição de servidores por raça/cor +select nome_cor as cor_raca, count(nome_cor) as quantidade_servidores +from {{ ref("servidores_completos") }} +group by nome_cor +order by quantidade_servidores desc diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_situacao_funcional.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_situacao_funcional.sql new file mode 100644 index 00000000..80d24aec --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_situacao_funcional.sql @@ -0,0 +1,32 @@ +with + dados_funcionais_enriquecidos as ( + select distinct + df.*, + case + when df.modalidade_pgd is null + then 'Não participa' + when df.modalidade_pgd = 'parcial' + then 'Parcial' + when df.modalidade_pgd = 'integral' + then 'Integral' + when df.modalidade_pgd = 'presencial' + then 'Presencial' + when df.modalidade_pgd = 'no exterior' + then 'No exterior' + end as pdg, + case + when df.nome_situacao_funcional = 'ATIVO EM OUTRO ORGAO' + then 'Ativo em outro órgão' + else df.sigla_uorg_exercicio + end as unidade_exercicio, + du.nome_municipio_uorg + from {{ ref("dados_funcionais") }} df + inner join {{ ref("dados_uorg") }} du on df.sigla_uorg_exercicio = du.sigla_uorg + ) + +select + nome_situacao_funcional as situacao_funcional_original, + count(nome_situacao_funcional) as quantidade_servidores +from dados_funcionais_enriquecidos +group by nome_situacao_funcional +order by quantidade_servidores desc diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/kpis_servidores.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/kpis_servidores.sql new file mode 100644 index 00000000..1d39e80d --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/kpis_servidores.sql @@ -0,0 +1,47 @@ +with + total_servidores as ( + select count(distinct cpf) as total from {{ ref("dados_funcionais") }} + ), + + servidores_ativos as ( + select count(distinct cpf) as total + from {{ ref("dados_funcionais") }} + where nome_situacao_funcional in ('ATIVO PERMANENTE') + ), + + aposentados as ( + select count(distinct cpf) as total + from {{ ref("dados_funcionais") }} + where nome_situacao_funcional in ('APOSENTADO') + ), + + estagiarios as ( + select count(distinct cpf) as total + from {{ ref("dados_funcionais") }} + where nome_situacao_funcional in ('ESTAGIARIO SIGEPE') + ), + + terceirizados as (select count(distinct id) as total from {{ ref("terceirizados") }}) + +select 'total_servidores' as kpi, total as valor +from total_servidores + +union all + +select 'servidores_ativos_permanentes' as kpi, total as valor +from servidores_ativos + +union all + +select 'aposentados' as kpi, total as valor +from aposentados + +union all + +select 'estagiarios' as kpi, total as valor +from estagiarios + +union all + +select 'terceirizados' as kpi, total as valor +from terceirizados diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/schema.yml b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/schema.yml index 646e78af..d18433df 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/schema.yml @@ -197,3 +197,138 @@ models: description: > Quantidade total de servidores distintos (com base no CPF) que se enquadram na combinação dos atributos anteriores. + + - name: distribuicao_genero + description: > + Tabela agregada que apresenta a distribuição percentual de servidores por gênero, + enriquecida com informações de modalidade PGD, escolaridade e deficiência física. + A análise considera apenas servidores ativos com dados completos nas bases do SIAPE, + SIORG e dados de uorg. A granularidade é por gênero, com cálculo de percentual em relação ao total. + + meta: + tags: + - gold + columns: + - name: genero + description: Gênero do servidor (masculino, feminino, etc.) conforme cadastrado nos dados pessoais. + - name: quantidade_servidores + description: Quantidade total de servidores pertencentes a este gênero. + - name: percentual_distribuicao + description: > + Percentual de servidores deste gênero em relação ao total de servidores, + calculado como COUNT(*) * 1.0 / SUM(COUNT(*)) OVER (). + + + - name: distribuicao_raca_cor + description: > + Tabela agregada que apresenta a distribuição de servidores por raça/cor, + enriquecida com informações de modalidade PGD, escolaridade e deficiência física. + A análise considera apenas servidores ativos com dados completos nas bases do SIAPE, + SIORG e dados de uorg. A granularidade é por raça/cor, ordenada pela quantidade de servidores. + + meta: + tags: + - gold + columns: + - name: cor_raca + description: > + Cor ou raça do servidor (branca, parda, preta, amarela, indígena, não informada) + conforme autodeclaração registrada nos dados pessoais do SIAPE. + - name: quantidade_servidores + description: Quantidade total de servidores que se autodeclararam desta cor/raça. + + + - name: distribuicao_situacao_funcional + description: > + Tabela agregada que apresenta a distribuição de servidores por situação funcional, + categorizando e padronizando as diferentes situações registradas no SIAPE. + As situações funcionais são normalizadas em categorias como Ativo permanente, Cedido, + Requisitado, Aposentado, Pensionista, Cargo comissionado e Estagiário. + A análise inclui dados enriquecidos com informações de modalidade PGD e unidade organizacional. + + meta: + tags: + - gold + columns: + - name: situacao_funcional + description: > + Situação funcional normalizada do servidor (Ativo permanente, Cedido, Requisitado, + Aposentado, Pensionista, Cargo comissionado, Estagiário), derivada do campo + nome_situacao_funcional através de regras de categorização. + - name: situacao_funcional_original + description: > + Nome original da situação funcional conforme registrado no SIAPE, antes da normalização + (ATIVO PERMANENTE, ATIVO EM OUTRO ORGAO, APOSENTADO, etc.). + - name: quantidade_servidores + description: Quantidade total de servidores que se enquadram nesta combinação de situação funcional. + + + - name: kpis_servidores + description: > + Tabela de KPIs (Key Performance Indicators) consolidados sobre o quadro de pessoal, + incluindo métricas de contagem total de servidores, servidores ativos permanentes, + aposentados, estagiários e terceirizados. Esta tabela fornece uma visão rápida e consolidada + dos principais indicadores quantitativos de recursos humanos da organização. + Cada linha representa um KPI específico com seu respectivo valor. + + meta: + tags: + - gold + - kpi + columns: + - name: kpi + description: > + Nome do indicador (total_servidores, servidores_ativos_permanentes, aposentados, + estagiarios, terceirizados). + - name: valor + description: > + Valor numérico do KPI, representando a contagem de CPFs ou IDs únicos conforme a métrica. + + + - name: distribuicao_mapa_uf + description: > + Tabela com a distribuição geográfica completa de servidores por Unidade Federativa (UF). + Retorna todos os 27 estados brasileiros com suas respectivas contagens de servidores + e percentuais. Estados sem servidores aparecem com valor 0. Útil para visualizações + em mapas e análises de distribuição geográfica do quadro de pessoal. + + meta: + tags: + - gold + - dashboard + columns: + - name: sigla_uf + description: Sigla da Unidade Federativa (AC, AL, AM, AP, BA, CE, DF, ES, GO, MA, MG, MS, MT, PA, PB, PE, PI, PR, RJ, RN, RO, RR, RS, SC, SE, SP, TO). + - name: nome_uf + description: Nome completo da Unidade Federativa em maiúsculas. + - name: valor + description: Quantidade total de servidores com unidade de exercício neste estado. + - name: percentual + description: Percentual de servidores deste estado em relação ao total, formatado como string com símbolo '%'. + + + - name: tabela_servidores_agregada + description: > + Tabela agregada de servidores com informações consolidadas por cargo, gênero, + situação funcional e localização (cidade/estado). Cada linha representa uma + combinação única desses atributos com a respectiva contagem de servidores. + Útil para análises detalhadas e exibição em tabelas de dashboard. + + meta: + tags: + - gold + - dashboard + columns: + - name: cargo + description: Nome do cargo ocupado pelo servidor. + - name: genero + description: Gênero do servidor normalizado (Masculino, Feminino). + - name: situacao + description: Situação funcional normalizada (Ativo Permanente, Aposentado, Ativo em outro órgão, Estagiário, Cedido/Requisitado). + - name: cidade + description: Nome do município da unidade de exercício formatado em título (primeira letra maiúscula). + - name: estado + description: Sigla da UF da unidade de exercício em maiúsculas. + - name: total + description: Quantidade total de servidores únicos (CPF) nesta combinação de atributos. + diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/tabela_servidores_agregada.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/tabela_servidores_agregada.sql new file mode 100644 index 00000000..d9927289 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/tabela_servidores_agregada.sql @@ -0,0 +1,59 @@ +-- Modelo para gerar tabela de servidores com agregação por cargo, gênero, situação e +-- localização +-- Agrupa os dados de servidores para visualização em tabelas detalhadas +with + servidores_completos as ( + select + df.cpf, + df.nome_cargo, + dp.nome_sexo as genero, + df.nome_situacao_funcional as situacao, + du.nome_municipio_uorg as cidade, + du.uf_uorg as estado + from {{ ref("dados_funcionais") }} df + inner join {{ ref("dados_pessoais") }} dp on df.cpf = dp.cpf + inner join {{ ref("dados_uorg") }} du on df.sigla_uorg_exercicio = du.sigla_uorg + where + df.nome_cargo is not null + and dp.nome_sexo is not null + and df.nome_situacao_funcional is not null + and du.nome_municipio_uorg is not null + and du.uf_uorg is not null + ), + + servidores_agregados as ( + select + nome_cargo as cargo, + case + when upper(genero) = 'MASCULINO' + then 'Masculino' + when upper(genero) = 'FEMININO' + then 'Feminino' + else genero + end as genero, + case + when upper(situacao) = 'ATIVO PERMANENTE' + then 'Ativo Permanente' + when upper(situacao) = 'APOSENTADO' + then 'Aposentado' + when upper(situacao) = 'ATIVO EM OUTRO ORGAO' + then 'Ativo em outro órgão' + when upper(situacao) = 'ESTAGIARIO SIGEPE' + then 'Estagiário' + when + upper(situacao) like '%CEDIDO%' + or upper(situacao) like '%REQUISITADO%' + then 'Cedido/Requisitado' + else situacao + end as situacao, + initcap(cidade) as cidade, + upper(estado) as estado, + count(distinct cpf) as total + from servidores_completos + group by nome_cargo, genero, situacao, cidade, estado + ) + +select cargo, genero, situacao, cidade, estado, total +from servidores_agregados +where total > 0 +order by total desc, cargo, genero diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/servidores_completos.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/servidores_completos.sql new file mode 100644 index 00000000..1ae2bc94 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/servidores_completos.sql @@ -0,0 +1,43 @@ +-- Modelo intermediário que centraliza os enriquecimentos de dados de servidores +-- Combina informações de hierarquia, dados funcionais, organizacionais e pessoais +-- Este modelo evita duplicação de código nos modelos gold +with + hierarquia_enriquecida as ( + select + ph.*, + case + when df.modalidade_pgd is null + then 'Não participa' + when df.modalidade_pgd = 'parcial' + then 'Parcial' + when df.modalidade_pgd = 'integral' + then 'Integral' + when df.modalidade_pgd = 'presencial' + then 'Presencial' + when df.modalidade_pgd = 'no exterior' + then 'No exterior' + end as pdg, + case + when ph.nome_situacao_funcional = 'ATIVO EM OUTRO ORGAO' + then 'Ativo em outro órgão' + else siglaunidade + end as unidade_exercicio + from {{ ref("hierarquia") }} ph + inner join {{ ref("dados_funcionais") }} df on ph.cpf = df.cpf + ), + + servidores_enriquecidos as ( + select distinct ph.*, du.nome_municipio_uorg + from hierarquia_enriquecida ph + inner join {{ ref("dados_uorg") }} du on ph.siglaunidade = du.sigla_uorg + order by caminho_unidade, hierarquia_cargo + ) + +select distinct + se.*, + sd.cod_escolaridade_principal, + sd.nome_escolaridade_principal, + sd.nome_deficiencia_fisica, + sd.nome_cargo as nome_cargo_emprego +from servidores_enriquecidos se +inner join {{ ref("servidores_detalhados") }} sd on se.cpf = sd.cpf diff --git a/airflow_lappis/dags/dbt/ipea/seeds/estados_brasil.csv b/airflow_lappis/dags/dbt/ipea/seeds/estados_brasil.csv new file mode 100644 index 00000000..eaa791fd --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/seeds/estados_brasil.csv @@ -0,0 +1,28 @@ +sigla_uf,nome_uf +AC,ACRE +AL,ALAGOAS +AP,AMAPÁ +AM,AMAZONAS +BA,BAHIA +CE,CEARÁ +DF,DISTRITO FEDERAL +ES,ESPÍRITO SANTO +GO,GOIÁS +MA,MARANHÃO +MT,MATO GROSSO +MS,MATO GROSSO DO SUL +MG,MINAS GERAIS +PA,PARÁ +PB,PARAÍBA +PR,PARANÁ +PE,PERNAMBUCO +PI,PIAUÍ +RJ,RIO DE JANEIRO +RN,RIO GRANDE DO NORTE +RS,RIO GRANDE DO SUL +RO,RONDÔNIA +RR,RORAIMA +SC,SANTA CATARINA +SP,SÃO PAULO +SE,SERGIPE +TO,TOCANTINS diff --git a/airflow_lappis/plugins/cliente_github.py b/airflow_lappis/plugins/cliente_github.py new file mode 100644 index 00000000..6d0e33d2 --- /dev/null +++ b/airflow_lappis/plugins/cliente_github.py @@ -0,0 +1,137 @@ +""" +Cliente para interagir com a API do GitHub. +""" + +import base64 +import logging +from typing import Dict, Any, Optional + +import requests + + +class ClienteGitHub: + """Cliente para operações com GitHub API.""" + + BASE_URL = "https://api.github.com" + + def __init__(self, token: str) -> None: + """ + Inicializa o cliente GitHub. + + Args: + token: Token de acesso pessoal do GitHub + """ + self.token = token + self.headers = { + "Authorization": f"token {token}", + "Accept": "application/vnd.github.v3+json", + } + logging.info("[cliente_github.py] Cliente GitHub inicializado") + + def get_file_sha( + self, owner: str, repo: str, path: str, branch: str = "main" + ) -> Optional[str]: + """ + Obtém o SHA de um arquivo no repositório. + + Args: + owner: Proprietário do repositório + repo: Nome do repositório + path: Caminho do arquivo no repositório + branch: Branch (padrão: main) + + Returns: + SHA do arquivo ou None se não existir + """ + url = f"{self.BASE_URL}/repos/{owner}/{repo}/contents/{path}" + params = {"ref": branch} + + try: + response = requests.get(url, headers=self.headers, params=params) + if response.status_code == 200: + data = response.json() + sha: Optional[str] = data.get("sha") + logging.info(f"[cliente_github.py] SHA obtido para {path}: {sha}") + return sha + elif response.status_code == 404: + logging.info(f"[cliente_github.py] Arquivo {path} não existe ainda") + return None + else: + logging.error( + f"[cliente_github.py] Erro ao obter SHA: {response.status_code} - " + f"{response.text}" + ) + return None + except Exception as e: + logging.error(f"[cliente_github.py] Erro ao obter SHA: {str(e)}") + return None + + def update_file( + self, + owner: str, + repo: str, + path: str, + content: str, + message: str, + branch: str = "main", + ) -> Dict[str, Any]: + """ + Cria ou atualiza um arquivo no repositório. + + Args: + owner: Proprietário do repositório + repo: Nome do repositório + path: Caminho do arquivo no repositório + content: Conteúdo do arquivo (será codificado em base64) + message: Mensagem do commit + branch: Branch (padrão: main) + + Returns: + Resposta da API do GitHub + + Raises: + Exception: Se houver erro na atualização + """ + url = f"{self.BASE_URL}/repos/{owner}/{repo}/contents/{path}" + + # Codificar conteúdo em base64 + content_bytes = content.encode("utf-8") + content_base64 = base64.b64encode(content_bytes).decode("utf-8") + + # Obter SHA do arquivo existente (necessário para atualização) + sha = self.get_file_sha(owner, repo, path, branch) + + # Preparar payload + payload = { + "message": message, + "content": content_base64, + "branch": branch, + } + + if sha: + payload["sha"] = sha + logging.info(f"[cliente_github.py] Atualizando arquivo existente: {path}") + else: + logging.info(f"[cliente_github.py] Criando novo arquivo: {path}") + + try: + response = requests.put(url, headers=self.headers, json=payload) + + if response.status_code in [200, 201]: + data: Dict[str, Any] = response.json() + logging.info( + f"[cliente_github.py] Arquivo {path} " + f"{'atualizado' if sha else 'criado'} com sucesso" + ) + return data + else: + error_msg = ( + f"Erro ao atualizar arquivo: {response.status_code} - " + f"{response.text}" + ) + logging.error(f"[cliente_github.py] {error_msg}") + raise Exception(error_msg) + + except Exception as e: + logging.error(f"[cliente_github.py] Erro ao atualizar arquivo: {str(e)}") + raise diff --git a/airflow_lappis/plugins/cliente_postgres.py b/airflow_lappis/plugins/cliente_postgres.py index e95c2082..41c47586 100755 --- a/airflow_lappis/plugins/cliente_postgres.py +++ b/airflow_lappis/plugins/cliente_postgres.py @@ -423,3 +423,149 @@ def execute_non_query(self, query: str) -> None: f"[cliente_postgres.py] Erro ao executar non-query. Erro: {e}" ) raise RuntimeError("Erro ao executar comando SQL sem retorno") from e + + def get_dashboard_kpis(self) -> Dict[str, int]: + """ + Busca os KPIs do dashboard de servidores. + + Returns: + Dict[str, int]: Dicionário com os KPIs + """ + query = "SELECT kpi, valor FROM pessoas.kpis_servidores" + + with psycopg2.connect(self.conn_str) as conn: + with conn.cursor() as cursor: + cursor.execute(query) + results = cursor.fetchall() + return {row[0]: row[1] for row in results} + + def get_dashboard_genero(self) -> Dict[str, float]: + """ + Busca a distribuição por gênero para o dashboard. + + Returns: + Dict[str, float]: Dicionário com percentuais por gênero + """ + query = """ + SELECT + genero, + ROUND(percentual_distribuicao * 100, 1) as percentual + FROM pessoas.distribuicao_genero + """ + + with psycopg2.connect(self.conn_str) as conn: + with conn.cursor() as cursor: + cursor.execute(query) + results = cursor.fetchall() + genero_data = {} + for row in results: + genero = row[0].lower() if row[0] else "n/a" + genero_data[f"{genero}_percent"] = float(row[1]) + return genero_data + + def get_dashboard_raca_cor(self) -> List[Dict[str, Any]]: + """ + Busca a distribuição por raça/cor para o dashboard. + + Returns: + List[Dict[str, Any]]: Lista de dicionários com raça/cor e quantidade + """ + query = """ + SELECT + COALESCE(cor_raca, 'NÃO DECLARADA') as nome_cor, + quantidade_servidores as valor + FROM pessoas.distribuicao_raca_cor + ORDER BY quantidade_servidores DESC + """ + + with psycopg2.connect(self.conn_str) as conn: + with conn.cursor() as cursor: + cursor.execute(query) + results = cursor.fetchall() + return [{"nome_cor": row[0], "valor": row[1]} for row in results] + + def get_dashboard_situacao_funcional(self) -> List[Dict[str, Any]]: + """ + Busca a distribuição por situação funcional para o dashboard. + + Returns: + List[Dict[str, Any]]: Lista de dicionários com situação e quantidade + """ + query = """ + SELECT + situacao_funcional_original as label, + quantidade_servidores as valor + FROM pessoas.distribuicao_situacao_funcional + ORDER BY quantidade_servidores DESC + """ + + with psycopg2.connect(self.conn_str) as conn: + with conn.cursor() as cursor: + cursor.execute(query) + results = cursor.fetchall() + return [{"label": row[0], "valor": row[1]} for row in results] + + def get_dashboard_mapa_uf(self) -> Dict[str, Dict[str, Any]]: + """ + Busca a distribuição geográfica por UF para o dashboard (mapa). + + Returns: + Dict[str, Dict[str, Any]]: Dicionário com UF como chave e dados como valor + """ + query = """ + SELECT + sigla_uf, + nome_uf, + valor, + percentual + FROM pessoas.distribuicao_mapa_uf + ORDER BY sigla_uf + """ + + with psycopg2.connect(self.conn_str) as conn: + with conn.cursor() as cursor: + cursor.execute(query) + results = cursor.fetchall() + return { + row[0]: {"nome": row[1], "valor": row[2], "percentual": row[3]} + for row in results + } + + def get_dashboard_tabela_servidores(self, limit: int = 100) -> List[Dict[str, Any]]: + """ + Busca dados agregados de servidores para exibição em tabela do dashboard. + + Args: + limit: Número máximo de registros a retornar (padrão: 100) + + Returns: + List[Dict[str, Any]]: Lista de dicionários com dados dos servidores + """ + query = """ + SELECT + cargo, + genero, + situacao, + cidade, + estado, + total + FROM pessoas.tabela_servidores_agregada + ORDER BY total DESC + LIMIT %s + """ + + with psycopg2.connect(self.conn_str) as conn: + with conn.cursor() as cursor: + cursor.execute(query, (limit,)) + results = cursor.fetchall() + return [ + { + "cargo": row[0], + "genero": row[1], + "situacao": row[2], + "cidade": row[3], + "estado": row[4], + "total": row[5], + } + for row in results + ] diff --git a/docker-compose.yml b/docker-compose.yml index 5492fb5b..b9249648 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -130,4 +130,4 @@ services: volumes: postgres-db: - airflow_logs: + airflow_logs: \ No newline at end of file From 3f811e254d0e827faf73e3b14d545b83fb712ef9 Mon Sep 17 00:00:00 2001 From: Tiago Bittencourt Date: Fri, 21 Nov 2025 17:51:41 -0300 Subject: [PATCH 177/317] =?UTF-8?q?feat(dags):=20paramentiza=C3=A7=C3=A3o?= =?UTF-8?q?=20do=20schedule=20das=20dags=20usando=20Airflow=20vars?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../contratos_inativos_ingest_dag.py | 3 ++- .../compras_gov/contratos_ingest_dag.py | 3 ++- .../compras_gov/cronograma_ingest_dag.py | 3 ++- .../compras_gov/empenhos_ingest_dag.py | 3 ++- .../compras_gov/faturas_ingest_dag.py | 3 ++- .../compras_gov/terceirizados_ingest_dag.py | 3 ++- .../siafi/nota_credito_siafi_ingest_dag.py | 3 ++- .../siafi/nota_empenho_siafi_ingest_dag.py | 3 ++- ...programacao_financeira_siafi_ingest_dag.py | 3 ++- ..._afastamento_historico_siape_ingest_dag.py | 5 +++- .../dados_afastamento_siape_ingest_dag.py | 3 ++- .../siape/dados_curriculo_siape_ingest_dag.py | 3 ++- .../dados_dependentes_siape_ingest_dag.py | 3 ++- .../siape/dados_escolares_siape_ingest_dag.py | 3 ++- .../siape/dados_financeiros_siape_dag.py | 3 ++- .../dados_funcionais_siape_ingest_dag.py | 3 ++- .../siape/dados_pa_siape_ingest_dag.py | 3 ++- .../siape/dados_pessoais_siape_ingest_dag.py | 3 ++- .../siape/dados_uorg_siape_ingest_dag.py | 3 ++- .../lista_aposentadoria_siape_ingest_dag.py | 3 ++- .../lista_servidores_siape_ingest_dag.py | 3 ++- .../siape/lista_uorgs_siape_ingest_dag.py | 3 ++- .../pensoes_instituidas_siape_ingest_dag.py | 3 ++- .../siorg/cargos_funcao_ingest_dag.py | 5 +++- ...rutura_organizacional_cargos_ingest_dag.py | 3 ++- .../unidade_organizacional_ingest_dag.py | 3 ++- .../empenhos_tesouro_ingest_dag.py | 3 ++- .../nc_tesouro_ingest.dag.py | 3 ++- .../pf_tesouro_ingest_dag.py | 3 ++- .../visao_orcamentaria_ingest.py | 3 ++- .../notas_de_credito_ingest_dag.py | 3 ++- .../transfere_gov/plano_acao_ingest_dag.py | 3 ++- .../programa_beneficiario_ingest_dag.py | 3 ++- .../programacao_financeira_ingest_dag.py | 3 ++- .../transfere_gov/programas_ingest_dag.py | 3 ++- .../planos_acao_especiais_ingest_dag.py | 3 ++- .../programas_especiais_ingest_dag.py | 3 ++- airflow_lappis/plugins/schedule_loader.py | 27 +++++++++++++++++++ 38 files changed, 105 insertions(+), 37 deletions(-) create mode 100644 airflow_lappis/plugins/schedule_loader.py diff --git a/airflow_lappis/dags/data_ingest/compras_gov/contratos_inativos_ingest_dag.py b/airflow_lappis/dags/data_ingest/compras_gov/contratos_inativos_ingest_dag.py index 482fd29c..9f06fbc8 100755 --- a/airflow_lappis/dags/data_ingest/compras_gov/contratos_inativos_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/compras_gov/contratos_inativos_ingest_dag.py @@ -3,13 +3,14 @@ from airflow.decorators import dag, task from airflow.models import Variable from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_contratos import ClienteContratos from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("contratos_inativos_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/compras_gov/contratos_ingest_dag.py b/airflow_lappis/dags/data_ingest/compras_gov/contratos_ingest_dag.py index 4cce191c..ad789ce7 100755 --- a/airflow_lappis/dags/data_ingest/compras_gov/contratos_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/compras_gov/contratos_ingest_dag.py @@ -4,13 +4,14 @@ from airflow.operators.trigger_dagrun import TriggerDagRunOperator from airflow.models import Variable from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_contratos import ClienteContratos from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("contratos_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/compras_gov/cronograma_ingest_dag.py b/airflow_lappis/dags/data_ingest/compras_gov/cronograma_ingest_dag.py index aa623a20..5eeccfc1 100755 --- a/airflow_lappis/dags/data_ingest/compras_gov/cronograma_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/compras_gov/cronograma_ingest_dag.py @@ -1,13 +1,14 @@ import logging from airflow.decorators import dag, task from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_contratos import ClienteContratos from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("cronograma_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/compras_gov/empenhos_ingest_dag.py b/airflow_lappis/dags/data_ingest/compras_gov/empenhos_ingest_dag.py index 179dd261..828efb5e 100755 --- a/airflow_lappis/dags/data_ingest/compras_gov/empenhos_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/compras_gov/empenhos_ingest_dag.py @@ -1,13 +1,14 @@ import logging from airflow.decorators import dag, task from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_contratos import ClienteContratos from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("empenhos_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/compras_gov/faturas_ingest_dag.py b/airflow_lappis/dags/data_ingest/compras_gov/faturas_ingest_dag.py index 5529fc2b..62008d8f 100755 --- a/airflow_lappis/dags/data_ingest/compras_gov/faturas_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/compras_gov/faturas_ingest_dag.py @@ -1,13 +1,14 @@ import logging from airflow.decorators import dag, task from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule from cliente_contratos import ClienteContratos from cliente_postgres import ClientPostgresDB from postgres_helpers import get_postgres_conn @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("faturas_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/compras_gov/terceirizados_ingest_dag.py b/airflow_lappis/dags/data_ingest/compras_gov/terceirizados_ingest_dag.py index ed8d2512..34083e50 100644 --- a/airflow_lappis/dags/data_ingest/compras_gov/terceirizados_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/compras_gov/terceirizados_ingest_dag.py @@ -1,13 +1,14 @@ import logging from airflow.decorators import dag, task from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_contratos import ClienteContratos from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("terceirizados_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/siafi/nota_credito_siafi_ingest_dag.py b/airflow_lappis/dags/data_ingest/siafi/nota_credito_siafi_ingest_dag.py index 098bef97..242fe379 100644 --- a/airflow_lappis/dags/data_ingest/siafi/nota_credito_siafi_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siafi/nota_credito_siafi_ingest_dag.py @@ -1,12 +1,13 @@ from airflow.decorators import dag, task from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule from cliente_siafi import ClienteSiafi from cliente_postgres import ClientPostgresDB from postgres_helpers import get_postgres_conn @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("nota_credito_siafi_ingest_dag"), start_date=datetime(2024, 3, 12), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/siafi/nota_empenho_siafi_ingest_dag.py b/airflow_lappis/dags/data_ingest/siafi/nota_empenho_siafi_ingest_dag.py index 77ff5cad..32a9668a 100644 --- a/airflow_lappis/dags/data_ingest/siafi/nota_empenho_siafi_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siafi/nota_empenho_siafi_ingest_dag.py @@ -5,13 +5,14 @@ from airflow.models.param import Param from datetime import datetime, timedelta from typing import Dict, Any +from schedule_loader import get_dynamic_schedule from cliente_siafi import ClienteSiafi from cliente_postgres import ClientPostgresDB from postgres_helpers import get_postgres_conn @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("nota_empenho_siafi_ingest_dag"), start_date=datetime(2023, 3, 17), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/siafi/programacao_financeira_siafi_ingest_dag.py b/airflow_lappis/dags/data_ingest/siafi/programacao_financeira_siafi_ingest_dag.py index 2749884a..4c277135 100644 --- a/airflow_lappis/dags/data_ingest/siafi/programacao_financeira_siafi_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siafi/programacao_financeira_siafi_ingest_dag.py @@ -1,12 +1,13 @@ from airflow.decorators import dag, task from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule from cliente_siafi import ClienteSiafi from cliente_postgres import ClientPostgresDB from postgres_helpers import get_postgres_conn @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("programacao_financeira_siafi_ingest_dag"), start_date=datetime(2024, 3, 12), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/siape/dados_afastamento_historico_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_afastamento_historico_siape_ingest_dag.py index a9fa1e12..290cb179 100644 --- a/airflow_lappis/dags/data_ingest/siape/dados_afastamento_historico_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/dados_afastamento_historico_siape_ingest_dag.py @@ -2,13 +2,16 @@ import logging from datetime import datetime, timedelta from airflow.decorators import dag, task +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_siape import ClienteSiape from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule( + "dados_afastamento_historico_siape_ingest_dag" + ), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/siape/dados_afastamento_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_afastamento_siape_ingest_dag.py index 3c91dbb2..46acd096 100644 --- a/airflow_lappis/dags/data_ingest/siape/dados_afastamento_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/dados_afastamento_siape_ingest_dag.py @@ -2,13 +2,14 @@ import logging from datetime import datetime, timedelta from airflow.decorators import dag, task +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_siape import ClienteSiape from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("dados_afastamento_siape_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/siape/dados_curriculo_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_curriculo_siape_ingest_dag.py index f2713ea3..ab8fcde2 100644 --- a/airflow_lappis/dags/data_ingest/siape/dados_curriculo_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/dados_curriculo_siape_ingest_dag.py @@ -2,13 +2,14 @@ import logging from datetime import datetime, timedelta from airflow.decorators import dag, task +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_siape import ClienteSiape from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("dados_curriculo_siape_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/siape/dados_dependentes_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_dependentes_siape_ingest_dag.py index 1abbf1ca..dc198bc1 100644 --- a/airflow_lappis/dags/data_ingest/siape/dados_dependentes_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/dados_dependentes_siape_ingest_dag.py @@ -2,13 +2,14 @@ import logging from datetime import datetime, timedelta from airflow.decorators import dag, task +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_siape import ClienteSiape from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("dados_dependentes_siape_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/siape/dados_escolares_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_escolares_siape_ingest_dag.py index 5ed13643..09912607 100644 --- a/airflow_lappis/dags/data_ingest/siape/dados_escolares_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/dados_escolares_siape_ingest_dag.py @@ -2,13 +2,14 @@ import logging from datetime import datetime, timedelta from airflow.decorators import dag, task +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_siape import ClienteSiape from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("dados_escolares_siape_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/siape/dados_financeiros_siape_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_financeiros_siape_dag.py index fd38f5c6..cc20626b 100644 --- a/airflow_lappis/dags/data_ingest/siape/dados_financeiros_siape_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/dados_financeiros_siape_dag.py @@ -2,13 +2,14 @@ import logging from datetime import datetime, timedelta from airflow.decorators import dag, task +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_siape import ClienteSiape from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("dados_financeiros_siape_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/siape/dados_funcionais_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_funcionais_siape_ingest_dag.py index 3eb5cad2..8d5485a8 100644 --- a/airflow_lappis/dags/data_ingest/siape/dados_funcionais_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/dados_funcionais_siape_ingest_dag.py @@ -2,13 +2,14 @@ import logging from datetime import datetime, timedelta from airflow.decorators import dag, task +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_siape import ClienteSiape from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("dados_funcionais_siape_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/siape/dados_pa_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_pa_siape_ingest_dag.py index 3108cf45..4fb35850 100644 --- a/airflow_lappis/dags/data_ingest/siape/dados_pa_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/dados_pa_siape_ingest_dag.py @@ -2,13 +2,14 @@ import logging from datetime import datetime, timedelta from airflow.decorators import dag, task +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_siape import ClienteSiape from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("dados_pa_siape_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/siape/dados_pessoais_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_pessoais_siape_ingest_dag.py index 7a274ab3..d8447e5e 100644 --- a/airflow_lappis/dags/data_ingest/siape/dados_pessoais_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/dados_pessoais_siape_ingest_dag.py @@ -2,13 +2,14 @@ import logging from datetime import datetime, timedelta from airflow.decorators import dag, task +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_siape import ClienteSiape from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("dados_pessoais_siape_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/siape/dados_uorg_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/dados_uorg_siape_ingest_dag.py index 14500e64..1220ab79 100644 --- a/airflow_lappis/dags/data_ingest/siape/dados_uorg_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/dados_uorg_siape_ingest_dag.py @@ -2,13 +2,14 @@ import logging from datetime import datetime, timedelta from airflow.decorators import dag, task +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_siape import ClienteSiape from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("dados_uorg_siape_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/siape/lista_aposentadoria_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/lista_aposentadoria_siape_ingest_dag.py index 96d4bb6b..4afbb3be 100644 --- a/airflow_lappis/dags/data_ingest/siape/lista_aposentadoria_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/lista_aposentadoria_siape_ingest_dag.py @@ -3,13 +3,14 @@ import logging from datetime import datetime, timedelta from airflow.decorators import dag, task +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_siape import ClienteSiape from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("lista_aposentadoria_siape_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/siape/lista_servidores_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/lista_servidores_siape_ingest_dag.py index 9476c0e0..69eb340f 100644 --- a/airflow_lappis/dags/data_ingest/siape/lista_servidores_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/lista_servidores_siape_ingest_dag.py @@ -2,13 +2,14 @@ import logging from datetime import datetime, timedelta from airflow.decorators import dag, task +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_siape import ClienteSiape from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("lista_servidores_siape_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/siape/lista_uorgs_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/lista_uorgs_siape_ingest_dag.py index 0c1cc67a..539aa34b 100644 --- a/airflow_lappis/dags/data_ingest/siape/lista_uorgs_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/lista_uorgs_siape_ingest_dag.py @@ -3,13 +3,14 @@ from datetime import datetime from airflow.decorators import dag, task from datetime import timedelta +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_siape import ClienteSiape from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("lista_uorgs_siape_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/siape/pensoes_instituidas_siape_ingest_dag.py b/airflow_lappis/dags/data_ingest/siape/pensoes_instituidas_siape_ingest_dag.py index ca9ae310..0caa044e 100644 --- a/airflow_lappis/dags/data_ingest/siape/pensoes_instituidas_siape_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siape/pensoes_instituidas_siape_ingest_dag.py @@ -3,13 +3,14 @@ import requests from datetime import datetime, timedelta from airflow.decorators import dag, task +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_siape import ClienteSiape from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("pensoes_instituidas_siape_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/siorg/cargos_funcao_ingest_dag.py b/airflow_lappis/dags/data_ingest/siorg/cargos_funcao_ingest_dag.py index 27da9f22..b065b2f5 100644 --- a/airflow_lappis/dags/data_ingest/siorg/cargos_funcao_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siorg/cargos_funcao_ingest_dag.py @@ -1,13 +1,16 @@ import logging from airflow.decorators import dag, task from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_siorg import ClienteSiorg from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule( + "dados_afastamento_historico_siape_ingest_dag" + ), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/siorg/estrutura_organizacional_cargos_ingest_dag.py b/airflow_lappis/dags/data_ingest/siorg/estrutura_organizacional_cargos_ingest_dag.py index 05ceed40..861f1d71 100644 --- a/airflow_lappis/dags/data_ingest/siorg/estrutura_organizacional_cargos_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siorg/estrutura_organizacional_cargos_ingest_dag.py @@ -1,13 +1,14 @@ import logging from airflow.decorators import dag, task from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_siorg import ClienteSiorg from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("estrutura_organizacional_cargos_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={"retries": 1, "retry_delay": timedelta(minutes=5), "owner": "Davi"}, diff --git a/airflow_lappis/dags/data_ingest/siorg/unidade_organizacional_ingest_dag.py b/airflow_lappis/dags/data_ingest/siorg/unidade_organizacional_ingest_dag.py index 80f64097..e88ea27b 100755 --- a/airflow_lappis/dags/data_ingest/siorg/unidade_organizacional_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siorg/unidade_organizacional_ingest_dag.py @@ -1,13 +1,14 @@ import logging from airflow.decorators import dag, task from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_siorg import ClienteSiorg from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("unidade_organizacional_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_ingest_dag.py index 3ebeedf1..fc91e8a4 100755 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_ingest_dag.py @@ -5,6 +5,7 @@ from datetime import datetime, timedelta import logging import json +from schedule_loader import get_dynamic_schedule from cliente_email import fetch_and_process_email from cliente_postgres import ClientPostgresDB from postgres_helpers import get_postgres_conn @@ -50,7 +51,7 @@ dag_id="email_empenhos_tesouro_ingest", default_args=default_args, description="Processa anexos dos empenhos vindo do email, formata e insere no db", - schedule_interval="0 13 * * 1-6", + schedule_interval=get_dynamic_schedule("empenhos_tesouro_ingest_dag"), start_date=datetime(2023, 12, 1), catchup=False, tags=["email", "empenhos", "tesouro"], diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/nc_tesouro_ingest.dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/nc_tesouro_ingest.dag.py index b9973421..db850e75 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/nc_tesouro_ingest.dag.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/nc_tesouro_ingest.dag.py @@ -7,6 +7,7 @@ import json import pandas as pd import io +from schedule_loader import get_dynamic_schedule from cliente_email import fetch_and_process_email from cliente_postgres import ClientPostgresDB from postgres_helpers import get_postgres_conn @@ -62,7 +63,7 @@ dag_id="email_notas_credito_ingest", default_args=default_args, description="Processa anexos das NCs vindo de dois emails, formata e insere no db", - schedule_interval="0 13 * * 1-6", + schedule_interval=get_dynamic_schedule("nc_tesouro_ingest_dag"), start_date=datetime(2023, 12, 1), catchup=False, tags=["email", "ncs", "tesouro"], diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/pf_tesouro_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/pf_tesouro_ingest_dag.py index 3c1751ed..dd094853 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/pf_tesouro_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/pf_tesouro_ingest_dag.py @@ -7,6 +7,7 @@ import json import pandas as pd import io +from schedule_loader import get_dynamic_schedule from cliente_email import fetch_and_process_email from cliente_postgres import ClientPostgresDB from postgres_helpers import get_postgres_conn @@ -54,7 +55,7 @@ dag_id="email_programacoes_financeiras_ingest", default_args=default_args, description="Processa anexos das PFs vindo de dois emails, formata e insere no db", - schedule_interval="0 13 * * 1-6", + schedule_interval=get_dynamic_schedule("pf_tesouro_ingest_dag"), start_date=datetime(2023, 12, 1), catchup=False, tags=["email", "pfs", "tesouro"], diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/visao_orcamentaria_ingest.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/visao_orcamentaria_ingest.py index 79bb18b4..5819781a 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/visao_orcamentaria_ingest.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/visao_orcamentaria_ingest.py @@ -9,6 +9,7 @@ import io import re import zipfile +from schedule_loader import get_dynamic_schedule from cliente_email import fetch_email_with_zip from cliente_postgres import ClientPostgresDB from postgres_helpers import get_postgres_conn @@ -65,7 +66,7 @@ "DAG processa anexos da visão orçamentária total IPEA " "vindo do email, formata e insere no db" ), - schedule_interval="0 13 * * 1-6", + schedule_interval=get_dynamic_schedule("visao_orcamentaria_ingest"), start_date=datetime(2023, 12, 1), catchup=False, tags=["email", "visao_orcamentaria", "tesouro"], diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/notas_de_credito_ingest_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/notas_de_credito_ingest_dag.py index 3efb024e..b8af4b6e 100644 --- a/airflow_lappis/dags/data_ingest/transfere_gov/notas_de_credito_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transfere_gov/notas_de_credito_ingest_dag.py @@ -1,13 +1,14 @@ import logging from airflow.decorators import dag, task from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_postgres import ClientPostgresDB from cliente_ted import ClienteTed @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("notas_de_credito_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/plano_acao_ingest_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/plano_acao_ingest_dag.py index a3c858e1..4ef35978 100644 --- a/airflow_lappis/dags/data_ingest/transfere_gov/plano_acao_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transfere_gov/plano_acao_ingest_dag.py @@ -1,13 +1,14 @@ import logging from airflow.decorators import dag, task from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_ted import ClienteTed from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("plano_acao_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/programa_beneficiario_ingest_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/programa_beneficiario_ingest_dag.py index 3fc00436..c8e936f8 100644 --- a/airflow_lappis/dags/data_ingest/transfere_gov/programa_beneficiario_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transfere_gov/programa_beneficiario_ingest_dag.py @@ -1,13 +1,14 @@ import logging from airflow.decorators import dag, task from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_ted import ClienteTed from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("programa_beneficiario_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/programacao_financeira_ingest_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/programacao_financeira_ingest_dag.py index 789e29f5..a86a0e26 100644 --- a/airflow_lappis/dags/data_ingest/transfere_gov/programacao_financeira_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transfere_gov/programacao_financeira_ingest_dag.py @@ -1,13 +1,14 @@ import logging from airflow.decorators import dag, task from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_postgres import ClientPostgresDB from cliente_ted import ClienteTed @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("programacao_financeira_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/programas_ingest_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/programas_ingest_dag.py index 3a44235e..5aeaaba1 100644 --- a/airflow_lappis/dags/data_ingest/transfere_gov/programas_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transfere_gov/programas_ingest_dag.py @@ -2,13 +2,14 @@ from airflow.decorators import dag, task from airflow.models import Variable from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_ted import ClienteTed from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("programas_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/planos_acao_especiais_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/planos_acao_especiais_ingest_dag.py index 5a8d1117..19db1cab 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/planos_acao_especiais_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/planos_acao_especiais_ingest_dag.py @@ -1,13 +1,14 @@ import logging from airflow.decorators import dag, task from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_transferegov_emendas import ClienteTransfereGov from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("planos_acao_especiais_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/programas_especiais_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/programas_especiais_ingest_dag.py index dc176e5c..adda0a64 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/programas_especiais_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/programas_especiais_ingest_dag.py @@ -1,13 +1,14 @@ import logging from airflow.decorators import dag, task from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule from postgres_helpers import get_postgres_conn from cliente_transferegov_emendas import ClienteTransfereGov from cliente_postgres import ClientPostgresDB @dag( - schedule_interval="@daily", + schedule_interval=get_dynamic_schedule("programas_especiais_ingest_dag"), start_date=datetime(2023, 1, 1), catchup=False, default_args={ diff --git a/airflow_lappis/plugins/schedule_loader.py b/airflow_lappis/plugins/schedule_loader.py new file mode 100644 index 00000000..fe5c1d10 --- /dev/null +++ b/airflow_lappis/plugins/schedule_loader.py @@ -0,0 +1,27 @@ +from airflow.models import Variable +from datetime import timedelta + + +def get_dynamic_schedule(dag_id: str) -> str | timedelta: + """ + Retorna o schedule da Variable 'dynamic_schedules' para a DAG. + Suporta: 'preset'/'cron' (retorna str) e 'timedelta' (retorna timedelta). + """ + + schedules = Variable.get("dynamic_schedules", default_var={}, deserialize_json=True) + + dag_schedule = schedules.get(dag_id) + + if not dag_schedule: + raise ValueError(f"Nenhum schedule configurado para a DAG {dag_id}") + + dag_type = dag_schedule.get("type") + dag_value = dag_schedule.get("value") + + if dag_type in ["preset", "cron"]: + return str(dag_value) + + if dag_type == "timedelta": + return timedelta(**dag_value) + + raise ValueError(f"Tipo de schedule inválido: {dag_type}") From 6df0667c08989e6fc2fb419a23ec005cb8d82486 Mon Sep 17 00:00:00 2001 From: arthrok Date: Mon, 1 Dec 2025 00:11:36 -0300 Subject: [PATCH 178/317] refactor(ci): refatora ci corrigindo stages e jobs --- .github/workflow/main.yaml | 228 ----------------------------------- .github/workflows/main.yaml | 230 ++++++++++++++++++++++++++++++++++++ .gitlab-ci.yml | 130 -------------------- 3 files changed, 230 insertions(+), 358 deletions(-) delete mode 100644 .github/workflow/main.yaml create mode 100644 .github/workflows/main.yaml delete mode 100644 .gitlab-ci.yml diff --git a/.github/workflow/main.yaml b/.github/workflow/main.yaml deleted file mode 100644 index a43c74b7..00000000 --- a/.github/workflow/main.yaml +++ /dev/null @@ -1,228 +0,0 @@ -name: Python CI/CD - -on: - pull_request: - branches: - - main - types: [opened, synchronize, reopened] - push: - branches: - - main - paths: - - 'Dockerfile' - - 'requirements.txt' - - 'pyproject.toml' - workflow_dispatch: - -env: - PYTHON_VERSION: "3.11" - POETRY_VERSION: "1.8.5" - POETRY_VIRTUALENVS_IN_PROJECT: "true" - POETRY_CACHE_DIR: "/home/runner/.cache/poetry" - DBT_PROJECT_DIR: "${{ github.workspace }}/airflow_lappis/dags/dbt/ipea" - IMAGE_TAG: "ghcr.io/${{ github.repository }}/airflow-ipea:${{ github.sha }}" - -jobs: - analise_de_codigo: - name: Analisar Código - runs-on: ubuntu-latest - steps: - - name: Baixar o Repositório - uses: actions/checkout@v4 - - - name: Configuração do Python - uses: actions/setup-python@v5 - with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: Cache de Dependências do Poetry - uses: actions/cache@v4 - with: - path: ${{ env.POETRY_CACHE_DIR }} - key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }} - restore-keys: | - ${{ runner.os }}-poetry- - - - name: Instalar Dependências do Projeto - run: | - pip install poetry==${{ env.POETRY_VERSION }} - poetry config virtualenvs.in-project true - poetry install --no-root --with dev --no-interaction - - - name: Executar Linter (Black, Flake8, MyPy, etc.) - run: poetry run make lint - - --- - - testes_unitarios: - name: Executar Testes Unitários - runs-on: ubuntu-latest - steps: - - name: Baixar o Repositório - uses: actions/checkout@v4 - - - name: Configurar Python - uses: actions/setup-python@v5 - with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: Cache de Dependências do Poetry - uses: actions/cache@v4 - with: - path: ${{ env.POETRY_CACHE_DIR }} - key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }} - restore-keys: | - ${{ runner.os }}-poetry- - - - name: Instalar Dependências do Projeto - run: | - pip install poetry==${{ env.POETRY_VERSION }} - poetry config virtualenvs.in-project true - poetry install --no-root --with dev --no-interaction - - - name: Executar Pytest - run: poetry run pytest tests --junitxml=report.xml --cov-report=xml:coverage.xml - - - name: Enviar Relatório de Testes - uses: actions/upload-artifact@v4 - with: - name: relatorio-junit - path: report.xml - - - name: Enviar Relatório de Cobertura - uses: actions/upload-artifact@v4 - with: - name: relatorio-cobertura - path: coverage.xml - - --- - - analise_sonarcloud: - name: Análise com SonarCloud - runs-on: ubuntu-latest - if: github.event_name == 'pull_request' - steps: - - name: Baixar o Repositório - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Configurar Python - uses: actions/setup-python@v5 - with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: Instalar Dependências do Projeto - run: | - pip install poetry==${{ env.POETRY_VERSION }} - poetry install --no-root --with dev --no-interaction - - - name: Gerar Relatório de Cobertura - run: poetry run pytest tests --cov-report=xml:coverage.xml - - - name: Executar Análise SonarCloud - uses: SonarSource/sonarcloud-github-action@v2.2.0 - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - SONAR_ORGANIZATION: ${{ secrets.SONAR_ORG }} - with: - projectKey: ${{ secrets.SONAR_PROJECT_KEY }} - args: > - -Dsonar.exclusions=**/.cache/poetry/**/*,**/.venv/**/*,**/.cache/pip/**/* - -Dsonar.python.coverage.reportPaths=coverage.xml - - --- - - construir_e_publicar_docker: - name: Construir & Publicar Imagem Docker - runs-on: ubuntu-latest - needs: [analise_de_codigo, testes_unitarios, analise_sonarcloud] - if: github.ref == 'refs/heads/main' || github.event_name == 'pull_request' - steps: - - name: Baixar o Repositório - uses: actions/checkout@v4 - - - name: Configurar Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Acessar o GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Construir e Publicar Imagem Docker - uses: docker/build-push-action@v5 - with: - push: true - tags: ${{ env.IMAGE_TAG }} - context: . - - --- - - deploy_docs_dbt: - name: Deploy da Documentação do DBT - runs-on: ubuntu-latest - needs: [construir_e_publicar_docker] - if: github.ref == 'refs/heads/main' - steps: - - name: Baixar o Repositório - uses: actions/checkout@v4 - - - name: Instalar Dependências do DBT - run: | - sudo apt-get update - sudo apt-get -y install openvpn iputils-ping - pip install dbt-core dbt-postgres - - - name: Conectar à VPN Privada - env: - VPN_P12: ${{ secrets.VPN_P12 }} - VPN_USER: ${{ secrets.VPN_USER }} - VPN_PWD: ${{ secrets.VPN_PWD }} - CLIENT_OVPN: ${{ secrets.CLIENT_OVPN }} - run: | - mkdir -p /etc/openvpn - echo "$CLIENT_OVPN" > /etc/openvpn/client.ovpn - echo "$VPN_P12" | base64 --decode > /etc/openvpn/openvpn_ipea_vpn.p12 - chmod 600 /etc/openvpn/openvpn_ipea_vpn.p12 - echo "$VPN_USER" >/etc/openvpn/cred.txt - echo "$VPN_PWD" >>/etc/openvpn/cred.txt - chmod 600 /etc/openvpn/cred.txt - sudo openvpn --config /etc/openvpn/client.ovpn --auth-user-pass /etc/openvpn/cred.txt --verb 3 --daemon --log /tmp/ovpn.log - timeout=60 - until grep -q 'Initialization Sequence Completed' /tmp/ovpn.log; do - sleep 1 - (( timeout-- )) - if (( timeout == 0 )); then - echo "❌ VPN não inicializou em 60s" >&2 - cat /tmp/ovpn.log - exit 1 - fi - done - ping -c 4 10.0.0.73 || true - - - name: Gerar Documentação do DBT - run: | - cd ${{ env.DBT_PROJECT_DIR }} - dbt deps - dbt docs generate - mkdir -p "${{ github.workspace }}/public" - mv target/* "${{ github.workspace }}/public/" - - - name: Fazer o Upload dos Artefatos para o GitHub Pages - uses: actions/upload-pages-artifact@v3 - with: - path: 'public' - - - name: Finalizar Processo da VPN - if: always() - run: sudo pkill openvpn || true - - - name: Concluir Deploy para o GitHub Pages - uses: actions/deploy-pages@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - - diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 00000000..e705b3dc --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,230 @@ +name: Python CI/CD + +on: + pull_request: + branches: [main] + push: + branches: [main] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +env: + PYTHON_VERSION: "3.11" + POETRY_VERSION: "1.8.5" + POETRY_VIRTUALENVS_IN_PROJECT: "true" + POETRY_CACHE_DIR: "/home/runner/.cache/poetry" + DBT_PROJECT_DIR: "${{ github.workspace }}/airflow_lappis/dags/dbt/ipea" + + IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/airflow-ipea + IMAGE_TAG_SHA: ${{ github.sha }} + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Cache Poetry + uses: actions/cache@v4 + with: + path: | + ${{ env.POETRY_CACHE_DIR }} + .venv + key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }} + restore-keys: | + ${{ runner.os }}-poetry- + + - name: Install deps + run: | + pip install poetry==${{ env.POETRY_VERSION }} + poetry config virtualenvs.in-project true + poetry install --no-root --with dev --no-interaction + + - name: Run lint + run: poetry run make lint + + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Cache Poetry + uses: actions/cache@v4 + with: + path: | + ${{ env.POETRY_CACHE_DIR }} + .venv + key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }} + restore-keys: | + ${{ runner.os }}-poetry- + + - name: Install deps + run: | + pip install poetry==${{ env.POETRY_VERSION }} + poetry config virtualenvs.in-project true + poetry install --no-root --with dev --no-interaction + + - name: Run tests + run: > + poetry run pytest tests + --junitxml=report.xml + --cov=. --cov-report=xml:coverage.xml + + - uses: actions/upload-artifact@v4 + with: + name: reports + path: | + report.xml + coverage.xml + + docker_build: + name: Docker build + runs-on: ubuntu-latest + needs: [lint, test] + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v3 + + - name: Build + uses: docker/build-push-action@v5 + with: + push: false + context: . + cache-from: type=gha + cache-to: type=gha,mode=max + tags: | + ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG_SHA }} + + docker_push: + name: Docker push + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: docker_build + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v3 + + - name: Login GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build & Push + uses: docker/build-push-action@v5 + with: + push: true + context: . + cache-from: type=gha + cache-to: type=gha,mode=max + tags: | + ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG_SHA }} + ${{ env.IMAGE_NAME }}:latest + + dbt_docs: + name: DBT Docs (deploy) + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: docker_build + permissions: + contents: read + pages: write + id-token: write + steps: + - name: Check VPN secrets + env: + CLIENT_OVPN: ${{ secrets.CLIENT_OVPN }} + run: | + if [ -z "$CLIENT_OVPN" ]; then + echo "VPN não configurada — pulando." + exit 0 + fi + + - uses: actions/checkout@v4 + + - name: Instalar deps (DBT + VPN) + run: | + sudo apt-get update + sudo apt-get -y install openvpn iputils-ping + pip install dbt-core dbt-postgres + + - name: Conectar VPN + env: + CLIENT_OVPN: ${{ secrets.CLIENT_OVPN }} + VPN_P12: ${{ secrets.VPN_P12 }} + VPN_USER: ${{ secrets.VPN_USER }} + VPN_PWD: ${{ secrets.VPN_PWD }} + run: | + sudo mkdir -p /etc/openvpn + echo "$CLIENT_OVPN" | sudo tee /etc/openvpn/client.ovpn >/dev/null + + echo "$VPN_USER" | sudo tee /etc/openvpn/cred.txt >/dev/null + echo "$VPN_PWD" | sudo tee -a /etc/openvpn/cred.txt >/dev/null + + echo "$VPN_P12" | base64 --decode | sudo tee /etc/openvpn/auth.p12 >/dev/null + sudo chmod 600 /etc/openvpn/* + + # garantir log legível ANTES de iniciar + sudo rm -f /tmp/ovpn.log || true + sudo touch /tmp/ovpn.log + sudo chmod 644 /tmp/ovpn.log + + sudo openvpn \ + --config /etc/openvpn/client.ovpn \ + --auth-user-pass /etc/openvpn/cred.txt \ + --pkcs12 /etc/openvpn/auth.p12 \ + --verb 3 --daemon --log /tmp/ovpn.log + + timeout=60 + until sudo grep -q 'Initialization Sequence Completed' /tmp/ovpn.log; do + sleep 1 + (( timeout-- )) + if (( timeout == 0 )); then + echo "❌ VPN não inicializou em 60s" >&2 + sudo tail -n 200 /tmp/ovpn.log || true + exit 1 + fi + done + + - name: Gerar DBT docs + env: + DB_DW_HOST: ${{ secrets.DB_DW_HOST }} + DB_DW_DBNAME: ${{ secrets.DB_DW_DBNAME }} + DB_DW_USER: ${{ secrets.DB_DW_USER }} + DB_DW_PASSWORD: ${{ secrets.DB_DW_PASSWORD }} + run: | + cd "${{ env.DBT_PROJECT_DIR }}" + dbt deps + dbt docs generate + mkdir -p public + mv target/* public/ + + - name: Finalizar VPN + if: always() + run: sudo pkill openvpn || true + + - uses: actions/upload-pages-artifact@v3 + with: + path: ${{ env.DBT_PROJECT_DIR }}/public + + - uses: actions/deploy-pages@v4 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 3aebcb73..00000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,130 +0,0 @@ -image: python:3.11-slim - -variables: - PYTHONPATH: "${CI_PROJECT_DIR}/dags:${CI_PROJECT_DIR}/plugins:./airflow/dags:./airflow/plugins" - MYPYPATH: "airflow_lappis/plugins:airflow_lappis/helpers:airflow_lappis/dags" - POETRY_HOME: "/opt/poetry" - POETRY_VERSION: "1.8.5" - POETRY_VIRTUALENVS_IN_PROJECT: "true" - POETRY_CACHE_DIR: "/tmp/poetry-cache" - PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" - DBT_PROJECT_DIR: "${CI_PROJECT_DIR}/airflow_lappis/dags/dbt/ipea" - GIT_CI_USER: "ci bot" - GIT_CI_EMAIL: "ci.lappis.rocks@gmail.com" - DOCKER_TLS_CERTDIR: "/certs" - IMAGE_TAG: "$CI_REGISTRY_IMAGE/airflow-ipea:$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA" - -cache: - paths: - - .cache/poetry - - .cache/pip - - .venv - - .cache/docker - -stages: - - lint - - test - - build - - deploy - -.install-poetry: - before_script: - - apt-get update - - apt-get -y install make - - pip install poetry==${POETRY_VERSION} - - poetry install --no-root --with dev - - source $(poetry env info --path)/bin/activate - -lint: - stage: lint - extends: .install-poetry - script: - - make lint - rules: - - if: $CI_PIPELINE_SOURCE == "merge_request_event" - -test: - stage: test - extends: .install-poetry - script: - - poetry run pytest tests --junitxml=report.xml - coverage: '/TOTAL.*?(\d+%)$/' - artifacts: - reports: - coverage_report: - coverage_format: cobertura - path: coverage.xml - junit: report.xml - rules: - - if: $CI_PIPELINE_SOURCE == "merge_request_event" - -scan: - stage: test - image: sonarsource/sonar-scanner-cli:latest - script: - - sonar-scanner -Dsonar.projectKey=$SONAR_PROJECT_KEY -Dsonar.organization=$SONAR_ORG -Dsonar.host.url=https://sonarcloud.io -Dsonar.login=$SONAR_TOKEN -Dsonar.exclusions=**/.cache/poetry/**/*,**/.venv/**/*,**/.cache/pip/**/* - rules: - - if: $CI_PIPELINE_SOURCE == "merge_request_event" - - changes: - - dags/** - - plugins/** - - src/** - - tests/** - -pages: - stage: deploy - image: python:3.11-slim - rules: - - if: $CI_COMMIT_BRANCH == "main" - before_script: - - apt-get update && apt-get install -y openvpn iputils-ping - - mkdir -p /etc/openvpn ci/dbt - - cat "$CLIENT_OVPN" >/etc/openvpn/client.ovpn - - cat "$VPN_P12" | base64 --decode > /etc/openvpn/openvpn_ipea_vpn.p12 - - chmod 600 /etc/openvpn/openvpn_ipea_vpn.p12 - - echo "$VPN_USER" >/etc/openvpn/cred.txt - - echo "$VPN_PWD" >>/etc/openvpn/cred.txt - - chmod 600 /etc/openvpn/cred.txt - - openvpn --config /etc/openvpn/client.ovpn --auth-user-pass /etc/openvpn/cred.txt --verb 3 --daemon --log /tmp/ovpn.log - - | - timeout=60 - until grep -q 'Initialization Sequence Completed' /tmp/ovpn.log; do - sleep 1 - (( timeout-- )) - if (( timeout == 0 )); then - echo "❌ VPN não inicializou em 60s" >&2 - cat /tmp/ovpn.log - exit 1 - fi - done - - ping -c 4 10.0.0.73 || true - - pip install dbt-core dbt-postgres - script: - - cd "$DBT_PROJECT_DIR" - - dbt deps - - dbt docs generate - - mkdir -p "${CI_PROJECT_DIR}/public" - - mv target/* "${CI_PROJECT_DIR}/public/" - - pkill openvpn || true - artifacts: - paths: - - public - expire_in: 1 week - -docker-build-and-push: - stage: build - image: docker:20.10.16 - services: - - docker:20.10.16-dind - script: - - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - - docker build -t $IMAGE_TAG . - - docker push $IMAGE_TAG - rules: - - if: $CI_PIPELINE_SOURCE == "merge_request_event" - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - - changes: - - Dockerfile - - requirements.txt - - pyproject.toml - \ No newline at end of file From 14b0128997f7dcbef2ef9bf26b7f7b3240635de2 Mon Sep 17 00:00:00 2001 From: arthrok Date: Mon, 1 Dec 2025 14:22:06 -0300 Subject: [PATCH 179/317] feat(ci): modulariza action do poetry --- .../workflows/actions/setup-poetry/action.yml | 38 +++++++++++++++ .github/workflows/main.yaml | 48 ++++--------------- 2 files changed, 48 insertions(+), 38 deletions(-) create mode 100644 .github/workflows/actions/setup-poetry/action.yml diff --git a/.github/workflows/actions/setup-poetry/action.yml b/.github/workflows/actions/setup-poetry/action.yml new file mode 100644 index 00000000..779ee67a --- /dev/null +++ b/.github/workflows/actions/setup-poetry/action.yml @@ -0,0 +1,38 @@ +name: Setup Poetry +description: Install Python, cache & install deps (dev) +runs: + using: composite + steps: + - uses: actions/setup-python@v5 + with: + python-version: ${{ inputs.python_version }} + + - name: Cache Poetry + uses: actions/cache@v4 + with: + path: | + ${{ inputs.poetry_cache_dir }} + .venv + key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }} + restore-keys: | + ${{ runner.os }}-poetry- + + - name: Install Poetry + deps + shell: bash + run: | + pip install poetry==${{ inputs.poetry_version }} + poetry config virtualenvs.in-project true + poetry install --no-root --with dev --no-interaction +inputs: + python_version: + description: Python version + required: true + default: "3.11" + poetry_version: + description: Poetry version + required: true + default: "1.8.5" + poetry_cache_dir: + description: Poetry cache dir + required: true + default: "~/.cache/poetry" diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index e705b3dc..089fd381 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -28,28 +28,15 @@ jobs: lint: name: Lint runs-on: ubuntu-latest + if: github.event_name == 'pull_request' steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: ./.github/actions/setup-poetry with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: Cache Poetry - uses: actions/cache@v4 - with: - path: | - ${{ env.POETRY_CACHE_DIR }} - .venv - key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }} - restore-keys: | - ${{ runner.os }}-poetry- - - - name: Install deps - run: | - pip install poetry==${{ env.POETRY_VERSION }} - poetry config virtualenvs.in-project true - poetry install --no-root --with dev --no-interaction + python_version: ${{ env.PYTHON_VERSION }} + poetry_version: ${{ env.POETRY_VERSION }} + poetry_cache_dir: ${{ env.POETRY_CACHE_DIR }} - name: Run lint run: poetry run make lint @@ -60,25 +47,11 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: ./.github/actions/setup-poetry with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: Cache Poetry - uses: actions/cache@v4 - with: - path: | - ${{ env.POETRY_CACHE_DIR }} - .venv - key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }} - restore-keys: | - ${{ runner.os }}-poetry- - - - name: Install deps - run: | - pip install poetry==${{ env.POETRY_VERSION }} - poetry config virtualenvs.in-project true - poetry install --no-root --with dev --no-interaction + python_version: ${{ env.PYTHON_VERSION }} + poetry_version: ${{ env.POETRY_VERSION }} + poetry_cache_dir: ${{ env.POETRY_CACHE_DIR }} - name: Run tests run: > @@ -153,7 +126,7 @@ jobs: steps: - name: Check VPN secrets env: - CLIENT_OVPN: ${{ secrets.CLIENT_OVPN }} + CLIENT_OVPN: ${{ secrets.CLIENT_OVPN }} run: | if [ -z "$CLIENT_OVPN" ]; then echo "VPN não configurada — pulando." @@ -184,7 +157,6 @@ jobs: echo "$VPN_P12" | base64 --decode | sudo tee /etc/openvpn/auth.p12 >/dev/null sudo chmod 600 /etc/openvpn/* - # garantir log legível ANTES de iniciar sudo rm -f /tmp/ovpn.log || true sudo touch /tmp/ovpn.log sudo chmod 644 /tmp/ovpn.log From d496251b653c09b631d821bf89bc3e759cc9ba28 Mon Sep 17 00:00:00 2001 From: Leonardo Bonetti <124631520+LeoFacB@users.noreply.github.com> Date: Tue, 2 Dec 2025 22:25:34 -0300 Subject: [PATCH 180/317] WIP: feat(transferegov): Adiciona ingestor de empenho especial (#45) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(transferegov): adiciona métodos para buscar empenhos especiais * feat(transferegov): adiciona DAG de ingestão de empenhos especiais --------- Co-authored-by: leonardo.bonetti --- .../empenhos_especiais_ingest_dag.py | 78 +++++++++++++++++++ .../plugins/cliente_transferegov_emendas.py | 76 ++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/transferegov_emendas/empenhos_especiais_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/empenhos_especiais_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/empenhos_especiais_ingest_dag.py new file mode 100644 index 00000000..3b742226 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/empenhos_especiais_ingest_dag.py @@ -0,0 +1,78 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule +from postgres_helpers import get_postgres_conn +from cliente_transferegov_emendas import ClienteTransfereGov +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval=get_dynamic_schedule("empenhos_especiais_ingest_dag"), + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Leonardo", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["transfere_gov_api", "empenhos_especiais"], +) +def api_empenhos_especiais_dag() -> None: + """DAG para buscar e armazenar empenhos especiais do Transfere Gov.""" + + @task + def fetch_and_store_empenhos_especiais() -> None: + logging.info( + "[empenhos_especiais_ingest_dag.py] Iniciando extração de " + "empenhos especiais" + ) + + api = ClienteTransfereGov() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + # Buscar IDs dos planos de ação especiais + query = ( + "SELECT DISTINCT id_plano_acao " + "FROM transferegov_emendas.planos_acao_especiais" + ) + planos_acao_ids = db.execute_query(query) + + if not planos_acao_ids: + logging.warning( + "[empenhos_especiais_ingest_dag.py] Nenhum plano de ação encontrado" + ) + return + + total_empenhos = 0 + for (id_plano_acao,) in planos_acao_ids: + logging.info( + f"[empenhos_especiais_ingest_dag.py] Buscando empenhos especiais " + f"para plano de ação {id_plano_acao}" + ) + + empenhos_data = api.get_all_empenhos_especiais_by_plano_acao(id_plano_acao) + + if empenhos_data: + for empenho in empenhos_data: + empenho["dt_ingest"] = datetime.now().isoformat() + + db.insert_data( + empenhos_data, + "empenhos_especiais", + conflict_fields=["id_empenho"], + primary_key=["id_empenho"], + schema="transferegov_emendas", + ) + total_empenhos += len(empenhos_data) + + logging.info( + f"[empenhos_especiais_ingest_dag.py] Concluído. " + f"Total: {total_empenhos} empenhos especiais inseridos/atualizados" + ) + + fetch_and_store_empenhos_especiais() + + +dag_instance = api_empenhos_especiais_dag() diff --git a/airflow_lappis/plugins/cliente_transferegov_emendas.py b/airflow_lappis/plugins/cliente_transferegov_emendas.py index f9fb3e11..e5893b65 100644 --- a/airflow_lappis/plugins/cliente_transferegov_emendas.py +++ b/airflow_lappis/plugins/cliente_transferegov_emendas.py @@ -185,3 +185,79 @@ def get_all_planos_acao_especiais_by_programa( f"{id_programa}: {len(all_data)}" ) return all_data + + def get_empenhos_especiais_by_plano_acao( + self, id_plano_acao: int, limit: int = 1000, offset: int = 0 + ) -> Optional[list]: + """ + Obter empenhos especiais por ID do plano de ação com paginação. + + Args: + id_plano_acao (int): ID do plano de ação + limit (int): Quantidade de registros por página (padrão: 1000) + offset (int): Deslocamento inicial (padrão: 0) + + Returns: + list: lista de empenhos especiais ou None se falhar + """ + endpoint = f"empenho_especial?id_plano_acao=eq.{id_plano_acao}" + params = {"select": "*", "limit": limit, "offset": offset} + + logging.info( + f"[cliente_transfere_gov.py] Fetching empenhos especiais for " + f"id_plano_acao={id_plano_acao}, limit={limit}, offset={offset}" + ) + + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER, params=params + ) + + if status == http.HTTPStatus.OK and isinstance(data, list): + logging.info( + f"[cliente_transfere_gov.py] Successfully fetched {len(data)} " + f"empenhos especiais for plano de ação {id_plano_acao}" + ) + return data + else: + logging.warning( + f"[cliente_transfere_gov.py] Failed to fetch empenhos especiais for " + f"plano de ação {id_plano_acao} with status: {status}" + ) + return None + + def get_all_empenhos_especiais_by_plano_acao( + self, id_plano_acao: int, page_size: int = 1000 + ) -> list: + """ + Obter todos os empenhos especiais de um plano de ação com paginação automática. + + Args: + id_plano_acao (int): ID do plano de ação + page_size (int): Quantidade de registros por requisição (padrão: 1000) + + Returns: + list: lista completa de empenhos especiais + """ + all_data = [] + offset = 0 + + while True: + data = self.get_empenhos_especiais_by_plano_acao( + id_plano_acao, limit=page_size, offset=offset + ) + + if not data or len(data) == 0: + break + + all_data.extend(data) + + if len(data) < page_size: + break + + offset += page_size + + logging.info( + f"[cliente_transfere_gov.py] Total empenhos especiais for plano de ação " + f"{id_plano_acao}: {len(all_data)}" + ) + return all_data From 97c6cc9316dde7f320dd5764132be87b39bf817b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateus=20Henrique=20Queiroz=20Magalh=C3=A3es=20Sousa?= <163928182+Mateushqms@users.noreply.github.com> Date: Tue, 2 Dec 2025 22:42:05 -0300 Subject: [PATCH 181/317] Feat/ingestao executor especial transferegov (#46) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(dag): adiciona dag para ingestão de executor especial Adiciona uma nova DAG para a ingestão de dados de 'executor especial' da API TransfereGov. Co-authored-by: Gxaite * feat(api): adiciona busca de executores por plano de ação Implementa o método no cliente da API TransfereGov. Co-authored-by: Gxaite * refactor: adiciona anotações de tipo para corrigir erro do mypy --------- Co-authored-by: Gxaite Co-authored-by: Davi de Aguiar Vieira <143732704+davi-aguiar-vieira@users.noreply.github.com> --- .../executor_especial_ingest_dag.py | 125 ++++++++++++++++++ .../plugins/cliente_transferegov_emendas.py | 55 ++++++++ 2 files changed, 180 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/transferegov_emendas/executor_especial_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/executor_especial_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/executor_especial_ingest_dag.py new file mode 100644 index 00000000..149f1c8a --- /dev/null +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/executor_especial_ingest_dag.py @@ -0,0 +1,125 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime +from postgres_helpers import get_postgres_conn +from cliente_transferegov_emendas import ClienteTransfereGov +from cliente_postgres import ClientPostgresDB + + +from typing import Iterator + + +CHUNK_SIZE = 200 + + +def chunk_list(lst: list, size: int) -> Iterator[list]: + """Divide uma lista em partes menores (chunks) de tamanho fixo.""" + for i in range(0, len(lst), size): + yield lst[i : i + size] + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Mateus e Gabriel", + "retries": 0, + # "retry_delay": timedelta(minutes=5), + }, + tags=["transfere_gov_api", "planos_acao_especiais"], +) +def api_executor_especial_dag() -> None: + + @task + def fetch_planos_acao() -> list: + """ + Busca todos os IDs dos planos de ação na base Postgres. + Cada ID será posteriormente usado para buscar executores na API. + """ + logging.info("[executor_especial] Buscando planos de ação...") + + db = ClientPostgresDB(get_postgres_conn()) + + query = """ + SELECT DISTINCT id_plano_acao + FROM transferegov_emendas.planos_acao_especiais + """ + + result = db.execute_query(query) + + # Converte lista de tuplas [(123,), (456,)] para [123, 456] + return [row[0] for row in result] + + @task + def split_chunks(planos_ids: list) -> list: + """ + Divide a lista de planos em múltiplos chunks para paralelizar via Airflow. + Ex.: 48k IDs → 240 tasks (se CHUNK_SIZE = 200) + """ + return list(chunk_list(planos_ids, CHUNK_SIZE)) + + @task + def process_chunk(chunk: list) -> None: + """ + Para cada chunk: + - Busca executores de cada plano de ação na API do TransfereGov + - Enrica dados com timestamp + - Remove duplicados + - Insere no Postgres com UPSERT + """ + api = ClienteTransfereGov() + db = ClientPostgresDB(get_postgres_conn()) + + timestamp = datetime.now().isoformat() + all_executores = [] + + # Processa cada plano de ação pertencente a este chunk + for plano_id in chunk: + logging.info(f"[executor_especial] Buscando executores do plano {plano_id}") + + executores = api.get_all_executores_especiais_by_plano_acao(plano_id) + + if not executores: + # Não há executores para este plano -> pula + continue + + # Adiciona metadados aos registros + for executor in executores: + executor["id_plano_acao"] = plano_id + executor["dt_ingest"] = timestamp + + all_executores.extend(executores) + + if not all_executores: + logging.info("[executor_especial] Chunk sem resultados.") + return + + logging.info( + f"[executor_especial] Inserindo {len(all_executores)} executores no Postgres." + ) + + # Remove duplicatas usando a chave (id_plano_acao, id_executor) + unique = {} + for row in all_executores: + key = (row["id_plano_acao"], row["id_executor"]) + unique[key] = row # se já existir, substitui e mantém apenas 1 + + all_executores = list(unique.values()) + + # Insere com UPSERT no Postgres + db.insert_data( + all_executores, + table_name="executor_especial", + conflict_fields=["id_plano_acao", "id_executor"], + primary_key=["id_plano_acao", "id_executor"], + schema="transferegov_emendas", + ) + + ids = fetch_planos_acao() # Busca todos os planos de ação + chunks = split_chunks(ids) # Divide em várias listas menores + process_chunk.expand(chunk=chunks) # Executa cada chunk em tasks paralelas + + +# Instancia a DAG para o Airflow carregar +dag_instance = api_executor_especial_dag() diff --git a/airflow_lappis/plugins/cliente_transferegov_emendas.py b/airflow_lappis/plugins/cliente_transferegov_emendas.py index e5893b65..abe374d9 100644 --- a/airflow_lappis/plugins/cliente_transferegov_emendas.py +++ b/airflow_lappis/plugins/cliente_transferegov_emendas.py @@ -186,6 +186,54 @@ def get_all_planos_acao_especiais_by_programa( ) return all_data + def get_executores_especiais_by_plano_acao( + self, id_plano_acao: int, limit: int = 1000, offset: int = 0 + ) -> Optional[list]: + + endpoint = f"executor_especial?id_plano_acao=eq.{id_plano_acao}" + params = {"select": "*", "limit": limit, "offset": offset} + + logging.info( + f"[cliente_transfere_gov.py] Fetching executores especiais " + f"for id_plano_acao={id_plano_acao}, limit={limit}, offset={offset}" + ) + + status, data = self.request( + http.HTTPMethod.GET, + endpoint, + headers=self.BASE_HEADER, + params=params, + ) + + if status == http.HTTPStatus.OK and isinstance(data, list): + return data + else: + logging.warning( + f"[cliente_transfere_gov.py] Failed to fetch executores especiais " + f"for plano_acao {id_plano_acao} with status {status}" + ) + return None + + def get_all_executores_especiais_by_plano_acao( + self, id_plano_acao: int, page_size: int = 1000 + ) -> list: + + all_data = [] + offset = 0 + page = 1 + + logging.info( + f"[cliente_transfere_gov.py] Starting extraction of executores especiais " + f"for plano_acao={id_plano_acao}" + ) + + while True: + logging.info( + f"[cliente_transfere_gov.py] Fetching page {page} (offset: {offset}) " + f"for plano_acao={id_plano_acao}" + ) + + data = self.get_executores_especiais_by_plano_acao( def get_empenhos_especiais_by_plano_acao( self, id_plano_acao: int, limit: int = 1000, offset: int = 0 ) -> Optional[list]: @@ -255,6 +303,13 @@ def get_all_empenhos_especiais_by_plano_acao( break offset += page_size + page += 1 + + logging.info( + f"[cliente_transfere_gov.py] Finished extraction. " + f"Total executores especiais for plano_acao {id_plano_acao}: {len(all_data)}" + ) + logging.info( f"[cliente_transfere_gov.py] Total empenhos especiais for plano de ação " From 29b12a60e93f21e4a39bc932ab2ba37b404c5de1 Mon Sep 17 00:00:00 2001 From: Mateus de Castro <140627829+mat054@users.noreply.github.com> Date: Wed, 3 Dec 2025 14:13:30 -0300 Subject: [PATCH 182/317] Feat ingest relatorio gestao e pncp (#48) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(clientes emendas): corrigindo erro no cliente transfere gov emendas * feat(cliente): metodo para buscar relatorio de gestao api transfere gov * feat(dag): dag de relatorio gestao do cliente transfere gov emendas * feat(cliente pncp): Criação do cliente pncp * feat(dag): dag principal para coleta dos dados do pncp relacionados ao ipea e suas contratacoes publicas * feat(dag): dag para coletas dos demais dados das contratacoes, incluindo itens e resultados dos itens de contratacao * feat(helper): Criação do helper safe request para tratar resultados de requisições que não eram tratados pelos demais helpers * fix(dag): correcao na dag de ingestao de relatorio de gestao * fix(cliente): retirando parte desnecessario do cliente postgres --- .../pncp/itens_resultados_licitacoes.py | 157 ++++++ .../data_ingest/pncp/licitacoes_ingest.py | 129 +++++ .../planos_acao_especiais_ingest_dag.py | 2 +- .../reltorio_gestao_ingest.py | 176 ++++++ airflow_lappis/helpers/safe_request.py | 79 +++ airflow_lappis/plugins/cliente_pncp.py | 506 ++++++++++++++++++ .../plugins/cliente_transferegov_emendas.py | 102 +++- 7 files changed, 1144 insertions(+), 7 deletions(-) create mode 100644 airflow_lappis/dags/data_ingest/pncp/itens_resultados_licitacoes.py create mode 100644 airflow_lappis/dags/data_ingest/pncp/licitacoes_ingest.py create mode 100644 airflow_lappis/dags/data_ingest/transferegov_emendas/reltorio_gestao_ingest.py create mode 100644 airflow_lappis/helpers/safe_request.py create mode 100644 airflow_lappis/plugins/cliente_pncp.py diff --git a/airflow_lappis/dags/data_ingest/pncp/itens_resultados_licitacoes.py b/airflow_lappis/dags/data_ingest/pncp/itens_resultados_licitacoes.py new file mode 100644 index 00000000..44ac7b2b --- /dev/null +++ b/airflow_lappis/dags/data_ingest/pncp/itens_resultados_licitacoes.py @@ -0,0 +1,157 @@ +import logging +from datetime import datetime, timedelta + +from airflow.decorators import dag, task + +from postgres_helpers import get_postgres_conn +from cliente_postgres import ClientPostgresDB +from cliente_pncp import ClientePNCP + + +def padronizar_colunas_json(lista_de_dicts: list[dict]) -> list[dict]: + """ + Padroniza uma lista de dicionários para garantir que todos tenham as mesmas chaves. + Caso alguma coluna esteja ausente, será preenchida com None (null no banco). + + Args: + lista_de_dicts: Lista de dicionários JSON já flattenizados. + + Returns: + Lista de dicionários padronizados com todas as chaves presentes. + """ + todas_as_chaves: set[str] = set() + for item in lista_de_dicts: + todas_as_chaves.update(item.keys()) + + for item in lista_de_dicts: + for chave in todas_as_chaves: + item.setdefault(chave, None) + + return lista_de_dicts + + +@dag( + schedule_interval="@daily", + start_date=datetime(2024, 12, 5), + catchup=False, + default_args={ + "owner": "Mateus", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["pncp", "compras_publicas", "itens_resultados"], +) +def pncp_contratacoes_itens_resultados_dag() -> None: + """ + DAG responsável por buscar **ITENS** e **RESULTADOS** das contratações no PNCP. + + Fluxo: + 1. Ler da tabela `pncp.contratacoes_publicacao` todos os valores de + `numeroControlePNCP`. + 2. Para cada controle encontrado, chamar a API PNCP para obter: + - Lista de itens de contratação + - Lista de resultados de cada item + 3. Persistir os dados em duas tabelas distintas no schema `pncp`: + - `pncp.contratacoes_itens` + - `pncp.contratacoes_resultados` + + Observações: + - Cada registro recebe o campo `dt_ingest` com a data/hora da ingestão. + - Conflitos são resolvidos via upsert (`ON CONFLICT`). + """ + + @task + def fetch_and_store_itens_resultados() -> None: + # --- Configuração de clientes --- + api = ClientePNCP() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + dt_ingest_iso = datetime.now().isoformat() + + # --- Obter lista de numeroControlePNCP --- + try: + lista_tuplas = db.execute_query( + "SELECT numerocontrolepncp FROM pncp.contratacoes_publicacao" + ) + except Exception as e: + logging.error( + "[pncp_itens_resultados_dag] Erro ao buscar numeroControlePNCP: %s", e + ) + raise + + lista_controles = [t[0] for t in lista_tuplas if t and t[0]] + + logging.info( + "[pncp_itens_resultados_dag] Iniciando coleta | total_controles=%d", + len(lista_controles), + ) + + # --- Coleta na API --- + try: + itens, resultados_brutos = api.get_itens_e_resultados(lista_controles) + # itens_flat = db._flatten_data(itens) + resultados_flat = db._flatten_data(resultados_brutos) + # itens = padronizar_colunas_json(itens_flat) + resultados = padronizar_colunas_json(resultados_flat) + except Exception as e: + logging.error( + "[pncp_itens_resultados_dag] Erro ao coletar dados da API PNCP: %s", e + ) + raise + + # --- Enriquecer com dt_ingest --- + for r in itens: + if isinstance(r, dict): + r["dt_ingest"] = dt_ingest_iso + for r in resultados: + if isinstance(r, dict): + r["dt_ingest"] = dt_ingest_iso + + # --- Persistência --- + if itens: + db.insert_data( + itens, + table_name="contratacoes_itens", + schema="pncp", + conflict_fields=["numeroItem", "numeroControlePNCP"], + primary_key=["numeroItem", "numeroControlePNCP"], + ) + logging.info("[pncp_itens_resultados_dag] Inseridos %d itens.", len(itens)) + else: + logging.warning( + "[pncp_itens_resultados_dag] Nenhum item retornado para inserção." + ) + + if resultados: + db.insert_data( + resultados, + table_name="contratacoes_resultados", + schema="pncp", + conflict_fields=[ + "numeroItem", + "numeroControlePNCPCompra", + "sequencialResultado", + "situacaoCompraItemResultadoId", + ], + primary_key=[ + "numeroItem", + "numeroControlePNCPCompra", + "sequencialResultado", + "situacaoCompraItemResultadoId", + ], + ) + logging.info( + "[pncp_itens_resultados_dag] Inseridos %d resultados.", len(resultados) + ) + else: + logging.warning( + "[pncp_itens_resultados_dag] Nenhum resultado retornado para inserção." + ) + + logging.info("[pncp_itens_resultados_dag] Processo concluído com sucesso.") + + fetch_and_store_itens_resultados() + + +dag_instance = pncp_contratacoes_itens_resultados_dag() \ No newline at end of file diff --git a/airflow_lappis/dags/data_ingest/pncp/licitacoes_ingest.py b/airflow_lappis/dags/data_ingest/pncp/licitacoes_ingest.py new file mode 100644 index 00000000..7d555afa --- /dev/null +++ b/airflow_lappis/dags/data_ingest/pncp/licitacoes_ingest.py @@ -0,0 +1,129 @@ +import logging +from datetime import datetime, timedelta + +from airflow.decorators import dag, task +from airflow.models import Variable +import yaml + +from postgres_helpers import get_postgres_conn +from cliente_postgres import ClientPostgresDB +from cliente_pncp import ClientePNCP + + +@dag( + schedule_interval="@daily", + start_date=datetime(2024, 12, 4), + catchup=False, + default_args={ + "owner": "Mateus", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["pncp", "compras_publicas"], +) +def pncp_publicacoes_dag() -> None: + """ + DAG para buscar publicações de contratações no PNCP e armazenar no PostgreSQL. + Pega `pncp_codigo_modalidade_contratacao` e `pncp_cnpj` das Airflow Variables. + """ + + @task + def fetch_and_store_pncp_publicacoes() -> None: + + orgao_alvo = Variable.get("airflow_orgao", default_var=None) + if not orgao_alvo: + logging.error("Variável airflow_orgao não definida!") + raise ValueError("airflow_orgao não definida") + + orgaos_config_str = Variable.get("airflow_variables", default_var="{}") + orgaos_config = yaml.safe_load(orgaos_config_str) + + orgao_cfg = orgaos_config.get(orgao_alvo, {}) + cnpj_orgao = orgao_cfg.get("orgao_pncp", []) + modalidades_list = orgao_cfg.get("modalidade_pncp", []) + + try: + cnpj_orgao_int = int(cnpj_orgao) + except ValueError: + logging.error( + "[pncp_publicacoes_dag] Variável pncp_codigo_modalidade_contratacao " + "inválida: %r", + cnpj_orgao, + ) + raise + + end_date = datetime.today() + start_date = end_date - timedelta(weeks=260) + + data_inicial = start_date.strftime("%Y%m%d") + data_final = end_date.strftime("%Y%m%d") + + logging.info( + "[pncp_publicacoes_dag] Iniciando coleta | modalidade=%s | cnpj=%s | " + "janela=[%s, %s]", + modalidades_list, + cnpj_orgao_int, + data_inicial, + data_final, + ) + + # --- Clientes/API e DB --- + api = ClientePNCP() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + for cod_modalidade in modalidades_list: + + try: + cod_modalidade_int = int(cod_modalidade) + except ValueError: + logging.error( + "[pncp_publicacoes_dag] Variável pncp_codigo_modalidade_contratacao " + "inválida: %r", + cod_modalidade, + ) + raise + # --- Coleta semestral com paginação interna do cliente --- + registros = api.get_contratacoes_publicacao_semestral( + data_inicial=data_inicial, + data_final=data_final, + codigo_modalidade_contratacao=cod_modalidade_int, + cnpj=cnpj_orgao_int, + ) + + if not registros: + logging.warning( + "[pncp_publicacoes_dag] Nenhum registro retornado do PNCP." + ) + pass + + # Enriquecer com dt_ingest + dt_ingest_iso = datetime.now().isoformat() + for r in registros: + # garante que é dict antes de setar a chave + if isinstance(r, dict): + r["dt_ingest"] = dt_ingest_iso + + # --- Persistência --- + logging.info( + "[pncp_publicacoes_dag] Inserindo %s registros no schema " + "pncp.tabela=contratacoes_publicacao", + len(registros), + ) + + # Se você conhecer a(s) PK(s) de fato, preencha em + # primary_key/conflict_fields. + db.insert_data( + registros, + table_name="contratacoes_publicacao", + conflict_fields=["numeroControlePNCP"], + primary_key=["numeroControlePNCP"], + schema="pncp", + ) + + logging.info("[pncp_publicacoes_dag] Inserção concluída com sucesso.") + + fetch_and_store_pncp_publicacoes() + + +dag_instance = pncp_publicacoes_dag() \ No newline at end of file diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/planos_acao_especiais_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/planos_acao_especiais_ingest_dag.py index 19db1cab..c8917578 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/planos_acao_especiais_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/planos_acao_especiais_ingest_dag.py @@ -33,7 +33,7 @@ def fetch_and_store_planos_acao_especiais() -> None: db = ClientPostgresDB(postgres_conn_str) # Buscar IDs dos programas especiais - query = "SELECT DISTINCT id_programa FROM transfere_gov.programas_especiais" + query = "SELECT DISTINCT id_programa FROM transferegov_emendas.programas_especiais" programas_ids = db.execute_query(query) if not programas_ids: diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/reltorio_gestao_ingest.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/reltorio_gestao_ingest.py new file mode 100644 index 00000000..1786e38b --- /dev/null +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/reltorio_gestao_ingest.py @@ -0,0 +1,176 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from typing import Iterator + +# Imports dos seus módulos personalizados +from postgres_helpers import get_postgres_conn +from cliente_transferegov_emendas import ClienteTransfereGov +from cliente_postgres import ClientPostgresDB + + +CHUNK_SIZE = 200 + + +def chunk_list(lst: list, size: int) -> Iterator[list]: + """Divide uma lista em partes menores (chunks) de tamanho fixo.""" + for i in range(0, len(lst), size): + yield lst[i : i + size] + + +@dag( + schedule_interval="@daily", + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Mateus", + "retries": 0, + # "retry_delay": timedelta(minutes=5), + }, + tags=["transfere_gov_api", "relatorio_gestao_especial"], +) +def api_relatorio_gestao_especial_dag() -> None: + + @task + def setup_table() -> None: + """ + Cria a tabela antes de qualquer processamento paralelo. + Evita race conditions nas tasks paralelas. + """ + logging.info("[relatorio_gestao] Configurando tabela...") + + db = ClientPostgresDB(get_postgres_conn()) + + # Remove a tabela antiga se existir (para garantir estrutura correta) + db.drop_table_if_exists( + table_name="relatorio_gestao_especial", + schema="transferegov_emendas" + ) + + # Cria tabela com TODOS os campos que virão da API + # Baseado na estrutura real retornada pela API TransfereGov + sample_data = { + "id_relatorio_gestao": "0", + "situacao_relatorio_gestao": "", + "parecer_relatorio_gestao": "", + "id_plano_acao": "0", + "dt_ingest": datetime.now().isoformat(), + } + + db.create_table_if_not_exists( + sample_data, + table_name="relatorio_gestao_especial", + primary_key=["id_relatorio_gestao"], + schema="transferegov_emendas", + ) + + logging.info("[relatorio_gestao] Tabela configurada com sucesso") + + @task + def fetch_planos_acao() -> list: + """ + Busca todos os IDs dos planos de ação na base Postgres. + Usa a mesma tabela base da DAG anterior para obter os IDs. + """ + logging.info("[relatorio_gestao] Buscando planos de ação...") + + db = ClientPostgresDB(get_postgres_conn()) + + # Mantive a mesma query da DAG anterior, pois o filtro é por plano de ação + query = """ + SELECT DISTINCT id_plano_acao + FROM transferegov_emendas.planos_acao_especiais + """ + + result = db.execute_query(query) + + # Converte lista de tuplas [(123,), (456,)] para [123, 456] + return [row[0] for row in result] + + @task + def split_chunks(planos_ids: list) -> list: + """ + Divide a lista de planos em múltiplos chunks para paralelizar via Airflow. + """ + return list(chunk_list(planos_ids, CHUNK_SIZE)) + + @task + def process_chunk(chunk: list) -> None: + """ + Para cada chunk: + - Busca relatórios de gestão de cada plano de ação na API + - Enrica dados com timestamp e id_plano_acao + - Remove duplicados + - Insere no Postgres com UPSERT + """ + api = ClienteTransfereGov() + db = ClientPostgresDB(get_postgres_conn()) + + timestamp = datetime.now().isoformat() + all_relatorios = [] + + # Processa cada plano de ação pertencente a este chunk + for plano_id in chunk: + logging.info(f"[relatorio_gestao] Buscando relatórios do plano {plano_id}") + + # Chama o novo método criado no cliente + relatorios = api.get_all_relatorio_gestao_especial_by_plano_acao(plano_id) + + if not relatorios: + # Não há relatórios para este plano -> pula + continue + + # Adiciona metadados aos registros + for item in relatorios: + # Garante que o ID do plano está no registro (caso a API não retorne no corpo) + item["id_plano_acao"] = plano_id + item["dt_ingest"] = timestamp + + all_relatorios.extend(relatorios) + + if not all_relatorios: + logging.info("[relatorio_gestao] Chunk sem resultados.") + return + + logging.info( + f"[relatorio_gestao] Inserindo {len(all_relatorios)} registros no Postgres." + ) + + # Remove duplicatas locais usando a PK (id_relatorio_gestao) + unique = {} + for row in all_relatorios: + # Baseado na imagem do Swagger, a PK parece ser id_relatorio_gestao + # Se quiser garantir unicidade por plano, pode usar tupla (id_plano_acao, id_relatorio_gestao) + key = row.get("id_relatorio_gestao") + if key: + unique[key] = row + else: + # Fallback caso venha sem ID (improvável, mas seguro) + logging.warning(f"Registro sem id_relatorio_gestao encontrado: {row}") + + all_relatorios_unique = list(unique.values()) + + if not all_relatorios_unique: + return + + # Insere com UPSERT no Postgres + db.insert_data( + all_relatorios_unique, + table_name="relatorio_gestao_especial", + # Define o conflito na PK da tabela + conflict_fields=["id_relatorio_gestao"], + primary_key=["id_relatorio_gestao"], + schema="transferegov_emendas", + ) + + setup = setup_table() # Cria a tabela primeiro + ids = fetch_planos_acao() # Busca todos os planos de ação + chunks = split_chunks(ids) # Divide em várias listas menores + + # Garante que a tabela é criada antes do processamento paralelo + setup >> ids + process_chunk.expand(chunk=chunks) # Executa cada chunk em tasks paralelas + + +# Instancia a DAG +dag_instance = api_relatorio_gestao_especial_dag() \ No newline at end of file diff --git a/airflow_lappis/helpers/safe_request.py b/airflow_lappis/helpers/safe_request.py new file mode 100644 index 00000000..78dceafd --- /dev/null +++ b/airflow_lappis/helpers/safe_request.py @@ -0,0 +1,79 @@ +from typing import Any, Optional, Tuple +from http import HTTPStatus +import logging +import time +import json +import httpx + + +def request_safe( + self: Any, method: str, path: str, **kwargs: Any +) -> Tuple[HTTPStatus, Optional[dict | list | str]]: + """ + Versão tolerante a 204 / corpo vazio / não-JSON / JSON inválido. + Usa atributos e client do `self` (DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES, + DEFAULT_SLEEP_SECONDS, client). + NÃO altera ClienteBase; apenas é chamada passando `self`. + """ + timeout = kwargs.get("timeout", getattr(self, "DEFAULT_TIMEOUT", 10)) + kwargs["timeout"] = timeout + + max_retries = getattr(self, "DEFAULT_MAX_RETRIES", 3) + sleep_s = getattr(self, "DEFAULT_SLEEP_SECONDS", 2) + + base_url = getattr(self, "base_url", "") # opcional, só para logs + + for attempt in range(max_retries): + try: + logging.info( + "[safe_request] Attempt %s %s %s%s | kwargs=%s", + attempt + 1, + method, + base_url, + path, + {k: v for k, v in kwargs.items() if k != "timeout"}, + ) + + resp = self.client.request(method, path, **kwargs) + status = HTTPStatus(resp.status_code) + + # 204 ou corpo vazio → não tentar json() + if status == HTTPStatus.NO_CONTENT or not resp.content: + logging.info("[safe_request] No content (status=%s)", status) + return status, None + + # Checar Content-Type + ct = (resp.headers.get("Content-Type") or "").lower() + if "application/json" not in ct: + preview = resp.text[:200] if resp.text else "" + logging.warning( + "[safe_request] Non-JSON content (status=%s, ct=%s) | preview=%r", + status, + ct, + preview, + ) + return status, resp.text + + # Tentar JSON com fallback + try: + return status, resp.json() + except json.JSONDecodeError as e: + preview = resp.text[:200] if resp.text else "" + logging.warning( + "[safe_request] Invalid JSON (status=%s): %s | preview=%r", + status, + e, + preview, + ) + return status, resp.text + + except httpx.HTTPError as e: + logging.warning("[safe_request] HTTPError on attempt %s: %s", attempt + 1, e) + if attempt < max_retries - 1: + time.sleep((attempt**2) * sleep_s) + else: + # última tentativa: não levanta exceção — retorna erro “seguro” + return HTTPStatus.SERVICE_UNAVAILABLE, f"request_error: {e}" + + # Fallback: não deveria chegar aqui, mas para satisfazer MyPy + return HTTPStatus.INTERNAL_SERVER_ERROR, "unexpected_error" \ No newline at end of file diff --git a/airflow_lappis/plugins/cliente_pncp.py b/airflow_lappis/plugins/cliente_pncp.py new file mode 100644 index 00000000..86a9c6fe --- /dev/null +++ b/airflow_lappis/plugins/cliente_pncp.py @@ -0,0 +1,506 @@ +import http +import logging +from typing import Any, Dict, List, Optional, Tuple +from cliente_base import ClienteBase +from safe_request import request_safe + + +# logging.basicConfig( +# level=logging.INFO, # ou DEBUG para depurar +# format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", +# handlers=[logging.StreamHandler(sys.stdout)], +# force=True # só no CLI; evita configs antigas bloqueando +# ) + +logger = logging.getLogger(__name__) + + +def parse_numero_controle(numero_controle: str) -> Tuple[str, str, str, str]: + """ + Recebe string no formato 'CNPJ-DIGITO-SEQUENCIAL/ANO' + e retorna (cnpj, digito, sequencial, ano). + """ + # Primeiro divide pelo '/' para separar ano + parte_esquerda, ano = numero_controle.split("/") + + # Depois divide a parte esquerda pelos '-' + cnpj, digito, sequencial = parte_esquerda.split("-") + + return cnpj, digito, sequencial, ano + + +def _ymd(ano: int, mes: int, dia: int) -> str: + """Formata YYYYMMDD com zero-padding correto.""" + return f"{ano:04d}{mes:02d}{dia:02d}" + + +class ClientePNCP(ClienteBase): + """ + Cliente para consultar publicações de contratações no PNCP. + + Documentação (resumo do uso): + - Base: https://pncp.gov.br/api/consulta + - Endpoint: /v1/contratacoes/publicacao + - Parâmetros (querystring): + dataInicial (yyyymmdd) + dataFinal (yyyymmdd) + codigoModalidadeContratacao (int) + uf (sigla do estado, ex.: 'DF') + codigoMunicipioIbge (int - 7 dígitos) + cnpj (apenas dígitos) + codigoUnidadeAdministrativa (int) + idUsuario (int) + pagina (int) + """ + + BASE_URL = "https://pncp.gov.br/api" + BASE_HEADER = {"accept": "*/*"} + + def __init__(self, rate_limit_per_min: int = 120) -> None: + super().__init__(base_url=ClientePNCP.BASE_URL) + logger.info( + "[cliente_pncp.py] Initialized ClientePNCP with base_url: %s", + ClientePNCP.BASE_URL, + ) + + def get_contratacoes_publicacao( + self, + data_inicial: str, + data_final: str, + codigo_modalidade_contratacao: Optional[int] = None, + uf: Optional[str] = None, + codigo_municipio_ibge: Optional[int] = None, + cnpj: Optional[str] = None, + codigo_unidade_administrativa: Optional[int] = None, + id_usuario: Optional[int] = None, + pagina: int = 1, + ) -> Tuple[List[Dict[str, Any]], int]: # <- mudei o tipo de retorno + """ + Busca publicações de contratações no PNCP (uma página). + + Returns: + (lista_itens, total_paginas) + """ + endpoint = "/consulta/v1/contratacoes/publicacao" + + params: Dict[str, Any] = { + "dataInicial": data_inicial, + "dataFinal": data_final, + "pagina": pagina, + } + params["codigoModalidadeContratacao"] = codigo_modalidade_contratacao + params["cnpj"] = cnpj + + logger.info( + "[cliente_pncp.py] Fetching PNCP | params=%s | pagina=%s", + {k: v for k, v in params.items() if k != "pagina"}, + pagina, + ) + + status, data = request_safe( + self, + http.HTTPMethod.GET, + endpoint, + headers={"accept": "application/json"}, + params=params, + ) + + # Se não veio 200, não tente decodificar estrutura + if status != http.HTTPStatus.OK: + logger.warning( + "[cliente_pncp.py] HTTP %s | pagina=%s | tipo=%s", + status, + pagina, + type(data).__name__, + ) + return [], 0 + + itens: List[Dict[str, Any]] = [] + total_paginas: int = 0 + + # 1) Se a API devolver uma lista direta + if isinstance(data, list): + itens = data + + # 2) Se vier envelopado em dict + elif isinstance(data, dict): + # tente chaves comuns para itens + key = "data" + val = data.get(key) + if isinstance(val, list): + itens = val + + # tente extrair total de páginas (se existir) + k = "totalPaginas" + if isinstance(data.get(k), int): + total_paginas = data[k] + + if not itens: + logger.warning( + "[cliente_pncp.py] 200 mas sem lista reconhecida. keys=%s", + list(data.keys()), + ) + + # 3) Se vier string/None/outro tipo → trate como vazio + else: + logger.warning( + "[cliente_pncp.py] 200 mas resposta não-JSON-list/dict | tipo=%s", + type(data).__name__, + ) + + logger.info( + "[cliente_pncp.py] OK | pagina=%s | rows=%s | total_paginas=%s", + pagina, + len(itens), + total_paginas, + ) + return itens, total_paginas + + def get_contratacoes_publicacao_paginado( + self, + data_inicial: str, + data_final: str, + codigo_modalidade_contratacao: Optional[int] = None, + uf: Optional[str] = None, + codigo_municipio_ibge: Optional[int] = None, + cnpj: Optional[str] = None, + codigo_unidade_administrativa: Optional[int] = None, + id_usuario: Optional[int] = None, + pagina_inicial: int = 1, + max_paginas: Optional[int] = None, + ) -> List[Dict[str, Any]]: + """ + Busca publicações de contratações no PNCP, agregando múltiplas páginas. + + Itera páginas até: + - retornar lista vazia/None, + - alcançar max_paginas (se fornecido). + + Returns: + list: Lista agregada com todas as linhas coletadas. + """ + agregados: List[Dict[str, Any]] = [] + pagina = pagina_inicial + paginas_coletadas = 0 + + while True: + if max_paginas is not None and paginas_coletadas >= max_paginas: + logger.info("[cliente_pncp.py] Max de páginas atingido: %s", max_paginas) + break + + page_data, max_paginas = self.get_contratacoes_publicacao( + data_inicial=data_inicial, + data_final=data_final, + codigo_modalidade_contratacao=codigo_modalidade_contratacao, + uf=uf, + codigo_municipio_ibge=codigo_municipio_ibge, + cnpj=cnpj, + codigo_unidade_administrativa=codigo_unidade_administrativa, + id_usuario=id_usuario, + pagina=pagina, + ) + + if not page_data: + logger.info( + "[cliente_pncp.py] Fim da paginação (vazio/None) na página %s", + pagina, + ) + break + + agregados.extend(page_data) + paginas_coletadas += 1 + pagina += 1 + + logger.info( + "[cliente_pncp.py] Coleta paginada concluída | total_paginas=%s | " + "total_registros=%s", + paginas_coletadas, + len(agregados), + ) + return agregados + + def get_contratacoes_publicacao_semestral( + self, + data_inicial: str, # 'YYYYMMDD' + data_final: str, # 'YYYYMMDD' + codigo_modalidade_contratacao: Optional[int] = None, + uf: Optional[str] = None, + codigo_municipio_ibge: Optional[int] = None, + cnpj: Optional[str] = None, + codigo_unidade_administrativa: Optional[int] = None, + id_usuario: Optional[int] = None, + pagina_inicial: int = 1, + max_paginas: Optional[int] = None, + ) -> List[Dict[str, Any]]: + """ + Varre semestralmente entre data_inicial e data_final usando janelas + half-open [início, fim). + Em cada janela, pagina até esgotar os resultados. + """ + logger.info( + "[PNCP][semestral] INÍCIO | intervalo_solicitado=[%s, %s] | " + "filtros: modalidade=%s, uf=%s, ibge=%s, cnpj=%s, ua=%s, usuario=%s | " + "pagina_inicial=%s, max_paginas=%s", + data_inicial, + data_final, + codigo_modalidade_contratacao, + uf, + codigo_municipio_ibge, + cnpj, + codigo_unidade_administrativa, + id_usuario, + pagina_inicial, + max_paginas, + ) + + agregados: List[Dict[str, Any]] = [] + try: + ano_ini = int(data_inicial[:4]) + ano_fim = int(data_final[:4]) + except Exception as e: + logger.error( + "[PNCP][semestral] ERRO ao parsear anos de data_inicial/data_final: %s", + e, + exc_info=True, + ) + raise + + limite_inicio = data_inicial + limite_fim = data_final + + logger.debug( + "[PNCP][semestral] anos_detectados: ano_ini=%s, ano_fim=%s | " + "limites_clip=[%s, %s]", + ano_ini, + ano_fim, + limite_inicio, + limite_fim, + ) + + for ano in range(ano_ini, ano_fim + 1): + logger.info("[PNCP][semestral] Ano %s → preparando janelas H1 e H2", ano) + + # H1: [ano-01-01, ano-07-01) + s1_ini = _ymd(ano, 1, 1) + s1_fim = _ymd(ano, 7, 1) + + # H2: [ano-07-01, (ano+1)-01-01) + s2_ini = _ymd(ano, 7, 1) + s2_fim = _ymd(ano + 1, 1, 1) + + # Clip com limites externos + s1_ini_clip = max(s1_ini, limite_inicio) + s1_fim_clip = min(s1_fim, limite_fim) + s2_ini_clip = max(s2_ini, limite_inicio) + s2_fim_clip = min(s2_fim, limite_fim) + + logger.debug( + "[PNCP][semestral] Ano %s | H1=[%s, %s) → clip=[%s, %s) | " + "H2=[%s, %s) → clip=[%s, %s)", + ano, + s1_ini, + s1_fim, + s1_ini_clip, + s1_fim_clip, + s2_ini, + s2_fim, + s2_ini_clip, + s2_fim_clip, + ) + + # --- H1 --- + if s1_ini_clip < s1_fim_clip: + logger.info( + "[PNCP][semestral] Ano %s | H1 CLIP válido: [%s, %s) → " + "iniciando coleta paginada", + ano, + s1_ini_clip, + s1_fim_clip, + ) + try: + page_data = self.get_contratacoes_publicacao_paginado( + data_inicial=s1_ini_clip, + data_final=s1_fim_clip, + codigo_modalidade_contratacao=codigo_modalidade_contratacao, + uf=uf, + codigo_municipio_ibge=codigo_municipio_ibge, + cnpj=cnpj, + codigo_unidade_administrativa=codigo_unidade_administrativa, + id_usuario=id_usuario, + pagina_inicial=pagina_inicial, + max_paginas=max_paginas, + ) + n = len(page_data) if page_data else 0 + logger.info( + "[PNCP][semestral] Ano %s | H1 coletado com sucesso | linhas=%s", + ano, + n, + ) + if page_data: + agregados.extend(page_data) + except Exception as e: + logger.error( + "[PNCP][semestral] Ano %s | H1 FALHOU: %s", ano, e, exc_info=True + ) + else: + logger.info( + "[PNCP][semestral] Ano %s | H1 CLIP vazio/ignorado: [%s, %s)", + ano, + s1_ini_clip, + s1_fim_clip, + ) + + # --- H2 --- + if s2_ini_clip < s2_fim_clip: + logger.info( + "[PNCP][semestral] Ano %s | H2 CLIP válido: [%s, %s) → " + "iniciando coleta paginada", + ano, + s2_ini_clip, + s2_fim_clip, + ) + try: + page_data = self.get_contratacoes_publicacao_paginado( + data_inicial=s2_ini_clip, + data_final=s2_fim_clip, + codigo_modalidade_contratacao=codigo_modalidade_contratacao, + uf=uf, + codigo_municipio_ibge=codigo_municipio_ibge, + cnpj=cnpj, + codigo_unidade_administrativa=codigo_unidade_administrativa, + id_usuario=id_usuario, + pagina_inicial=pagina_inicial, + max_paginas=max_paginas, + ) + n = len(page_data) if page_data else 0 + logger.info( + "[PNCP][semestral] Ano %s | H2 coletado com sucesso | linhas=%s", + ano, + n, + ) + if page_data: + agregados.extend(page_data) + except Exception as e: + logger.error( + "[PNCP][semestral] Ano %s | H2 FALHOU: %s", ano, e, exc_info=True + ) + else: + logger.info( + "[PNCP][semestral] Ano %s | H2 CLIP vazio/ignorado: [%s, %s)", + ano, + s2_ini_clip, + s2_fim_clip, + ) + + logger.info( + "[PNCP][semestral] FIM | anos=%s..%s | total_linhas=%s", + ano_ini, + ano_fim, + len(agregados), + ) + return agregados + + def get_itens_e_resultados( + self, lista_chaves: List[Tuple[str, int, str]] + ) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]: + """ + Recebe lista de numeroControlePNCP e retorna: + - lista com todos os itens de cada contratação + - lista com todos os resultados dos itens + + Args: + lista_chaves: lista de tuplas (cnpj, ano, sequencial) + + Returns: + Tuple: + - itens (List[Dict]) + - resultados (List[Dict]) + """ + itens_total: List[Dict[str, Any]] = [] + resultados_total: List[Dict[str, Any]] = [] + + for numeroControlePNCP in lista_chaves: + cnpj, digito, sequencial, ano = parse_numero_controle(numeroControlePNCP) + logger.info( + "[PNCP][itens/resultados] Iniciando para CNPJ=%s, ano=%s, seq=%s", + cnpj, + ano, + sequencial, + ) + + # 1) Buscar itens da contratação + endpoint_itens = f"/pncp/v1/orgaos/{cnpj}/compras/{ano}/{sequencial}/itens" + status, data_itens = request_safe( + self, http.HTTPMethod.GET, endpoint_itens, headers=self.BASE_HEADER + ) + + if status == http.HTTPStatus.OK and isinstance(data_itens, list): + for item in data_itens: + item["numeroControlePNCP"] = numeroControlePNCP + itens_total.extend(data_itens) + logger.info("[PNCP][itens] %s itens coletados", len(data_itens)) + else: + logger.warning( + "[PNCP][itens] Falha ao coletar itens | CNPJ=%s, ano=%s, seq=%s | " + "status=%s", + cnpj, + ano, + sequencial, + status, + ) + continue # pula para próxima chave + + # 2) Consultar quantidade de itens + endpoint_qtd = ( + f"/pncp/v1/orgaos/{cnpj}/compras/{ano}/{sequencial}/itens/quantidade" + ) + status, qtd = request_safe( + self, http.HTTPMethod.GET, endpoint_qtd, headers=self.BASE_HEADER + ) + # time.sleep(1) # Sleep after API call to avoid rate limiting + if status != http.HTTPStatus.OK or not isinstance(qtd, int): + logger.warning( + "[PNCP][quantidade] Não foi possível obter quantidade de itens | " + "CNPJ=%s, ano=%s, seq=%s", + cnpj, + ano, + sequencial, + ) + continue + + # Após o narrowing acima, qtd é garantidamente int + qtd_int: int = qtd # type: ignore[unreachable] + + # 3) Para cada item, buscar resultados + if qtd_int > 0: + for numero_item in range(1, qtd_int + 1): + endpoint_res = ( + f"/pncp/v1/orgaos/{cnpj}/compras/{ano}/{sequencial}/" + f"itens/{numero_item}/resultados" + ) + status, data_res = request_safe( + self, http.HTTPMethod.GET, endpoint_res, headers=self.BASE_HEADER + ) + # Sleep after API call to avoid rate limiting (commented out) + # time.sleep(1) + + if status == http.HTTPStatus.OK and isinstance(data_res, list): + # for r in data_res: + # r["numeroControlePNCP"] = numeroControlePNCP + resultados_total.extend(data_res) + logger.info( + "[PNCP][resultados] Item %s → %s resultados", + numero_item, + len(data_res), + ) + else: + logger.warning( + "[PNCP][resultados] Falha no item %s | CNPJ=%s, ano=%s, " + "seq=%s", + numero_item, + cnpj, + ano, + sequencial, + ) + + return itens_total, resultados_total \ No newline at end of file diff --git a/airflow_lappis/plugins/cliente_transferegov_emendas.py b/airflow_lappis/plugins/cliente_transferegov_emendas.py index abe374d9..c036b829 100644 --- a/airflow_lappis/plugins/cliente_transferegov_emendas.py +++ b/airflow_lappis/plugins/cliente_transferegov_emendas.py @@ -234,6 +234,27 @@ def get_all_executores_especiais_by_plano_acao( ) data = self.get_executores_especiais_by_plano_acao( + id_plano_acao, limit=page_size, offset=offset + ) + + if not data or len(data) == 0: + break + + all_data.extend(data) + + if len(data) < page_size: + break + + offset += page_size + page += 1 + + logging.info( + f"[cliente_transfere_gov.py] Finished extraction. " + f"Total executores especiais for plano_acao {id_plano_acao}: {len(all_data)}" + ) + + return all_data + def get_empenhos_especiais_by_plano_acao( self, id_plano_acao: int, limit: int = 1000, offset: int = 0 ) -> Optional[list]: @@ -303,16 +324,85 @@ def get_all_empenhos_especiais_by_plano_acao( break offset += page_size - page += 1 logging.info( - f"[cliente_transfere_gov.py] Finished extraction. " - f"Total executores especiais for plano_acao {id_plano_acao}: {len(all_data)}" + f"[cliente_transfere_gov.py] Total empenhos especiais for plano de ação " + f"{id_plano_acao}: {len(all_data)}" ) + return all_data + + def get_relatorio_gestao_especial_by_plano_acao( + self, id_plano_acao: int, limit: int = 1000, offset: int = 0 + ) -> Optional[list]: + """ + Obter relatórios de gestão especial por ID do plano de ação com paginação. + Endpoint: /relatorio_gestao_especial + """ + endpoint = f"relatorio_gestao_especial?id_plano_acao=eq.{id_plano_acao}" + params = {"select": "*", "limit": limit, "offset": offset} logging.info( - f"[cliente_transfere_gov.py] Total empenhos especiais for plano de ação " - f"{id_plano_acao}: {len(all_data)}" + f"[cliente_transfere_gov.py] Fetching relatorio_gestao_especial for " + f"id_plano_acao={id_plano_acao}, limit={limit}, offset={offset}" ) - return all_data + + status, data = self.request( + http.HTTPMethod.GET, + endpoint, + headers=self.BASE_HEADER, + params=params, + ) + + if status == http.HTTPStatus.OK and isinstance(data, list): + return data + else: + logging.warning( + f"[cliente_transfere_gov.py] Failed to fetch relatorio_gestao_especial " + f"for plano_acao {id_plano_acao} with status {status}" + ) + return None + + def get_all_relatorio_gestao_especial_by_plano_acao( + self, id_plano_acao: int, page_size: int = 1000 + ) -> list: + """ + Obter TODOS os relatórios de gestão especial de um plano de ação + com paginação automática (While True). + """ + all_data = [] + offset = 0 + page = 1 + + logging.info( + f"[cliente_transfere_gov.py] Starting extraction of relatorio_gestao_especial " + f"for plano_acao={id_plano_acao}" + ) + + while True: + logging.info( + f"[cliente_transfere_gov.py] Fetching page {page} (offset: {offset}) " + f"for plano_acao={id_plano_acao}" + ) + + data = self.get_relatorio_gestao_especial_by_plano_acao( + id_plano_acao, limit=page_size, offset=offset + ) + + if not data or len(data) == 0: + break + + all_data.extend(data) + + # Se vier menos registros que o page_size, chegamos ao fim + if len(data) < page_size: + break + + offset += page_size + page += 1 + + logging.info( + f"[cliente_transfere_gov.py] Finished extraction. " + f"Total relatorios gestao for plano_acao {id_plano_acao}: {len(all_data)}" + ) + return all_data \ No newline at end of file From 8f2e9b433c1856eac5b40ccf9c21f6365ce7c97d Mon Sep 17 00:00:00 2001 From: arthrok Date: Wed, 3 Dec 2025 21:17:36 -0300 Subject: [PATCH 183/317] feat(ci): adiciona sqlfluff no ci --- .github/workflows/main.yaml | 2 +- .sqlfluff.ci | 9 +++++++++ Makefile | 4 ++++ 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 .sqlfluff.ci diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 089fd381..daea9df3 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -39,7 +39,7 @@ jobs: poetry_cache_dir: ${{ env.POETRY_CACHE_DIR }} - name: Run lint - run: poetry run make lint + run: poetry run make lint-ci test: name: Test diff --git a/.sqlfluff.ci b/.sqlfluff.ci new file mode 100644 index 00000000..384a2e4d --- /dev/null +++ b/.sqlfluff.ci @@ -0,0 +1,9 @@ +[sqlfluff] +dialect = postgres +templater = jinja + +[sqlfluff:templater:jinja] +apply_dbt_builtins = True + +[sqlfluff:templater:jinja:context] +target_name = "ci" diff --git a/Makefile b/Makefile index f48d1c98..fb053dcb 100644 --- a/Makefile +++ b/Makefile @@ -22,5 +22,9 @@ lint: poetry run sqlfmt ./airflow_lappis/dags/dbt --check [ "${GITLAB_CI}" ] || poetry run sqlfluff lint ./airflow_lappis/dags/dbt +lint-ci: + poetry run sqlfmt ./airflow_lappis/dags/dbt --check + poetry run sqlfluff lint ./airflow_lappis/dags/dbt --config .sqlfluff.ci --ignore templating + test: poetry run pytest tests From dabb29469418cb7770a2ac2a460225f5267844f2 Mon Sep 17 00:00:00 2001 From: arthrok Date: Wed, 3 Dec 2025 21:19:03 -0300 Subject: [PATCH 184/317] fix(ci): corrige path da pasta de actions --- .github/{workflows => }/actions/setup-poetry/action.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{workflows => }/actions/setup-poetry/action.yml (100%) diff --git a/.github/workflows/actions/setup-poetry/action.yml b/.github/actions/setup-poetry/action.yml similarity index 100% rename from .github/workflows/actions/setup-poetry/action.yml rename to .github/actions/setup-poetry/action.yml From 2ad34926b20bfc9f99ed617454b0e8bd6af20f5a Mon Sep 17 00:00:00 2001 From: Tiago Bittencourt Date: Fri, 12 Dec 2025 17:40:25 -0300 Subject: [PATCH 185/317] feat(schedule): retorno default, evita dag break (#56) --- airflow_lappis/plugins/schedule_loader.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/airflow_lappis/plugins/schedule_loader.py b/airflow_lappis/plugins/schedule_loader.py index fe5c1d10..610afa03 100644 --- a/airflow_lappis/plugins/schedule_loader.py +++ b/airflow_lappis/plugins/schedule_loader.py @@ -2,10 +2,11 @@ from datetime import timedelta -def get_dynamic_schedule(dag_id: str) -> str | timedelta: +def get_dynamic_schedule(dag_id: str, default: str = "@daily") -> str | timedelta: """ Retorna o schedule da Variable 'dynamic_schedules' para a DAG. Suporta: 'preset'/'cron' (retorna str) e 'timedelta' (retorna timedelta). + Se não houver schedule configurado, retorna o valor default (@daily). """ schedules = Variable.get("dynamic_schedules", default_var={}, deserialize_json=True) @@ -13,7 +14,7 @@ def get_dynamic_schedule(dag_id: str) -> str | timedelta: dag_schedule = schedules.get(dag_id) if not dag_schedule: - raise ValueError(f"Nenhum schedule configurado para a DAG {dag_id}") + return default dag_type = dag_schedule.get("type") dag_value = dag_schedule.get("value") From 698557cf4d811fe9bfca1cbef81c2141165f4a87 Mon Sep 17 00:00:00 2001 From: arthrok Date: Tue, 6 Jan 2026 15:54:34 -0300 Subject: [PATCH 186/317] change(ci): bypass lint temporariamente --- .github/workflows/main.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index daea9df3..e86f46e3 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -39,7 +39,7 @@ jobs: poetry_cache_dir: ${{ env.POETRY_CACHE_DIR }} - name: Run lint - run: poetry run make lint-ci + run: poetry run make lint-ci || true test: name: Test From 0e09905974c53f781eeaf923e1b8e94c3d7bff67 Mon Sep 17 00:00:00 2001 From: arthrok Date: Tue, 6 Jan 2026 15:58:50 -0300 Subject: [PATCH 187/317] fix(ci): ajusta nome da imagem pra lower case --- .github/workflows/main.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index e86f46e3..1ad894c7 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -21,7 +21,8 @@ env: POETRY_CACHE_DIR: "/home/runner/.cache/poetry" DBT_PROJECT_DIR: "${{ github.workspace }}/airflow_lappis/dags/dbt/ipea" - IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/airflow-ipea + IMAGE_REGISTRY_OWNER: govhub-br + IMAGE_NAME: ghcr.io/govhub-br/airflow-ipea IMAGE_TAG_SHA: ${{ github.sha }} jobs: From 94840d1111abd674ba2dddb12179f30c50074a44 Mon Sep 17 00:00:00 2001 From: arthrok Date: Tue, 6 Jan 2026 16:16:36 -0300 Subject: [PATCH 188/317] change(ci): retira condicao de lint apenas em mr --- .github/workflows/main.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 1ad894c7..da1764a7 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -29,7 +29,6 @@ jobs: lint: name: Lint runs-on: ubuntu-latest - if: github.event_name == 'pull_request' steps: - uses: actions/checkout@v4 From a83d875a4851515be2f8d0659e23876327ed5a08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Henrique=20Egewarth?= Date: Mon, 19 Jan 2026 14:20:08 -0300 Subject: [PATCH 189/317] Create LICENSE (#64) --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..2fa07ff7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Gov Hub + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 3c51ea4dd19e335d6dc1be8de8ccc6fa018bc7d3 Mon Sep 17 00:00:00 2001 From: Tiago Bittencourt Date: Mon, 26 Jan 2026 14:33:09 -0300 Subject: [PATCH 190/317] =?UTF-8?q?feat(dag):=20extra=C3=A7=C3=A3o=20docum?= =?UTF-8?q?entos=20h=C3=A1beis=20transferegov=20(#57)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../documentos_habeis_especias_ingest_dag.py | 68 +++++++ .../plugins/cliente_transferegov_emendas.py | 171 ++++++++++++++++++ 2 files changed, 239 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/transferegov_emendas/documentos_habeis_especias_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/documentos_habeis_especias_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/documentos_habeis_especias_ingest_dag.py new file mode 100644 index 00000000..0a13960c --- /dev/null +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/documentos_habeis_especias_ingest_dag.py @@ -0,0 +1,68 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule +from postgres_helpers import get_postgres_conn +from cliente_transferegov_emendas import ClienteTransfereGov +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval=get_dynamic_schedule("documentos_habeis_especiais_ingest_dag"), + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Tiago", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["transfere_gov_api", "documentos_especiais"], +) +def api_documentos_habeis_especiais_dag() -> None: + """DAG para buscar e armazenar documentos hábeis especiais do Transfere Gov.""" + + @task + def fetch_and_store_documentos_habeis_especiais() -> None: + logging.info( + "[documentos_habeis_especiais_ingest_dag.py] Iniciando extração documentos " \ + "hábeis especiais" + ) + + api = ClienteTransfereGov() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + # Busca todos os documentos hábeis especiais com paginação automática + documentos_data = api.get_all_documentos_habeis_especiais(page_size=1000) + + if documentos_data and len(documentos_data) > 0: + # Adicionar dt_ingest a cada documento + for documento in documentos_data: + documento["dt_ingest"] = datetime.now().isoformat() + + # Inserir/atualizar dados no banco + logging.info( + f"[documentos_habeis_especiais_ingest_dag.py] Inserindo {len(documentos_data)} " + "documentos hábeis especiais no schema transfere_gov" + ) + db.insert_data( + documentos_data, + "documentos_habeis_especiais", + conflict_fields=["id_dh"], + primary_key=["id_dh"], + schema="transferegov_emendas", + ) + + logging.info( + f"[documentos_habeis_especiais_ingest_dag.py] Concluído. Total de " + f"{len(documentos_data)} documentos hábeis especiais inseridos/atualizados" + ) + else: + logging.warning( + "[documentos_habeis_especiais_ingest_dag.py] Nenhum documento hábil " \ + "especial encontrado" + ) + + fetch_and_store_documentos_habeis_especiais() + +dag_instance = api_documentos_habeis_especiais_dag() diff --git a/airflow_lappis/plugins/cliente_transferegov_emendas.py b/airflow_lappis/plugins/cliente_transferegov_emendas.py index c036b829..4c06c8f9 100644 --- a/airflow_lappis/plugins/cliente_transferegov_emendas.py +++ b/airflow_lappis/plugins/cliente_transferegov_emendas.py @@ -405,4 +405,175 @@ def get_all_relatorio_gestao_especial_by_plano_acao( f"[cliente_transfere_gov.py] Finished extraction. " f"Total relatorios gestao for plano_acao {id_plano_acao}: {len(all_data)}" ) + return all_data + + def get_documentos_habeis_especiais( + self, limit: int = 1000, offset: int = 0 + ) -> Optional[list]: + """ + Obter documentos hábeis especiais com paginação. + + Args: + limit (int): Quantidade de registros por página (padrão: 1000) + offset (int): Deslocamento inicial (padrão: 0) + + Returns: + list: lista de documentos hábeis especiais ou None se falhar + """ + endpoint = "documento_habil_especial" + params = { + "select": "*", + "order": "id_dh.asc", + "limit": limit, + "offset": offset, + } + + logging.info( + f"[cliente_transfere_gov.py] Fetching documentos hábeis especiais with " + f"limit={limit}, offset={offset}" + ) + + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER, params=params + ) + + if status == http.HTTPStatus.OK and isinstance(data, list): + logging.info( + f"[cliente_transfere_gov.py] Successfully fetched {len(data)} " + "documentos hábeis especiais" + ) + return data + else: + logging.warning( + f"[cliente_transfere_gov.py] Failed to fetch documentos hábeis especiais " + f"with status: {status}" + ) + return None + + def get_all_documentos_habeis_especiais(self, page_size: int = 1000) -> list: + """ + Obter todos os documentos hábeis especiais com paginação automática. + + Args: + page_size (int): Quantidade de registros por requisição (padrão: 1000) + + Returns: + list: lista completa de documentos hábeis especiais + """ + all_data = [] + offset = 0 + page = 1 + + logging.info( + "[cliente_transfere_gov.py] Starting full extraction of documentos hábeis especiais" + ) + + while True: + logging.info( + f"[cliente_transfere_gov.py] Fetching page {page} " f"(offset: {offset})" + ) + + data = self.get_documentos_habeis_especiais(limit=page_size, offset=offset) + + if not data or len(data) == 0: + logging.info( + "[cliente_transfere_gov.py] No more data received. " + "Extraction complete." + ) + break + + all_data.extend(data) + logging.info( + f"[cliente_transfere_gov.py] Page {page} fetched: {len(data)} records. " + f"Total so far: {len(all_data)}" + ) + + # Se recebemos menos registros que o limite, é a última página + if len(data) < page_size: + logging.info("[cliente_transfere_gov.py] Last page reached.") + break + + offset += page_size + page += 1 + + logging.info( + f"[cliente_transfere_gov.py] Extraction completed. " + f"Total records: {len(all_data)}" + ) + return all_data + + def get_documentos_habeis_especiais_by_empenho( + self, id_empenho: int, limit: int = 1000, offset: int = 0 + ) -> Optional[list]: + """ + Obter documentos hábeis especiais por ID do empenho com paginação. + + Args: + id_empenho (int): ID do empenho + limit (int): Quantidade de registros por página (padrão: 1000) + offset (int): Deslocamento inicial (padrão: 0) + + Returns: + list: lista de documentos hábeis especiais ou None se falhar + """ + endpoint = f"documento_habil_especial?id_empenho=eq.{id_empenho}" + params = {"select": "*", "limit": limit, "offset": offset} + + logging.info( + f"[cliente_transfere_gov.py] Fetching documentos hábeis especiais for " + f"id_empenho={id_empenho}, limit={limit}, offset={offset}" + ) + + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER, params=params + ) + + if status == http.HTTPStatus.OK and isinstance(data, list): + logging.info( + f"[cliente_transfere_gov.py] Successfully fetched {len(data)} " + f"documentos hábeis especiais for empenho {id_empenho}" + ) + return data + else: + logging.warning( + f"[cliente_transfere_gov.py] Failed to fetch documentos hábeis especiais for " + f"empenho {id_empenho} with status: {status}" + ) + return None + + def get_all_documentos_habeis_especiais_by_empenho( + self, id_empenho: int, page_size: int = 1000 + ) -> list: + """ + Obter todos os documentos hábeis especiais de um empenho com paginação automática. + + Args: + id_empenho (int): ID do empenho + page_size (int): Quantidade de registros por requisição (padrão: 1000) + + Returns: + list: lista completa de documentos hábeis especiais + """ + all_data = [] + offset = 0 + + while True: + data = self.get_documentos_habeis_especiais_by_empenho( + id_empenho, limit=page_size, offset=offset + ) + + if not data or len(data) == 0: + break + + all_data.extend(data) + + if len(data) < page_size: + break + + offset += page_size + + logging.info( + f"[cliente_transfere_gov.py] Total documentos hábeis especiais for empenho " + f"{id_empenho}: {len(all_data)}" + ) return all_data \ No newline at end of file From 31d605d77c3219bc9cf4ad5d8457de092d6b9161 Mon Sep 17 00:00:00 2001 From: Tiago Bittencourt Date: Mon, 26 Jan 2026 16:58:38 -0300 Subject: [PATCH 191/317] =?UTF-8?q?feat(metas):=20dag=20de=20extra=C3=A7?= =?UTF-8?q?=C3=A3o=20metas=5Fespeciais=20(#60)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(metas): dag de extração metas_especiais * fix: corrige erros de linting e formatação --------- Co-authored-by: davi-aguiar-vieira --- .../pncp/itens_resultados_licitacoes.py | 2 +- .../data_ingest/pncp/licitacoes_ingest.py | 2 +- .../documentos_habeis_especias_ingest_dag.py | 15 ++- .../metas_especiais_ingest_dag.py | 61 ++++++++++ .../planos_acao_especiais_ingest_dag.py | 4 +- .../reltorio_gestao_ingest.py | 89 ++++++++------- airflow_lappis/helpers/safe_request.py | 2 +- airflow_lappis/plugins/cliente_pncp.py | 2 +- .../plugins/cliente_transferegov_emendas.py | 107 ++++++++++++++++-- 9 files changed, 227 insertions(+), 57 deletions(-) create mode 100644 airflow_lappis/dags/data_ingest/transferegov_emendas/metas_especiais_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/pncp/itens_resultados_licitacoes.py b/airflow_lappis/dags/data_ingest/pncp/itens_resultados_licitacoes.py index 44ac7b2b..533d15b1 100644 --- a/airflow_lappis/dags/data_ingest/pncp/itens_resultados_licitacoes.py +++ b/airflow_lappis/dags/data_ingest/pncp/itens_resultados_licitacoes.py @@ -154,4 +154,4 @@ def fetch_and_store_itens_resultados() -> None: fetch_and_store_itens_resultados() -dag_instance = pncp_contratacoes_itens_resultados_dag() \ No newline at end of file +dag_instance = pncp_contratacoes_itens_resultados_dag() diff --git a/airflow_lappis/dags/data_ingest/pncp/licitacoes_ingest.py b/airflow_lappis/dags/data_ingest/pncp/licitacoes_ingest.py index 7d555afa..00f59bc6 100644 --- a/airflow_lappis/dags/data_ingest/pncp/licitacoes_ingest.py +++ b/airflow_lappis/dags/data_ingest/pncp/licitacoes_ingest.py @@ -126,4 +126,4 @@ def fetch_and_store_pncp_publicacoes() -> None: fetch_and_store_pncp_publicacoes() -dag_instance = pncp_publicacoes_dag() \ No newline at end of file +dag_instance = pncp_publicacoes_dag() diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/documentos_habeis_especias_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/documentos_habeis_especias_ingest_dag.py index 0a13960c..5d284fce 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/documentos_habeis_especias_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/documentos_habeis_especias_ingest_dag.py @@ -24,7 +24,7 @@ def api_documentos_habeis_especiais_dag() -> None: @task def fetch_and_store_documentos_habeis_especiais() -> None: logging.info( - "[documentos_habeis_especiais_ingest_dag.py] Iniciando extração documentos " \ + "[documentos_habeis_especiais_ingest_dag.py] Iniciando extração documentos " "hábeis especiais" ) @@ -42,8 +42,9 @@ def fetch_and_store_documentos_habeis_especiais() -> None: # Inserir/atualizar dados no banco logging.info( - f"[documentos_habeis_especiais_ingest_dag.py] Inserindo {len(documentos_data)} " - "documentos hábeis especiais no schema transfere_gov" + f"[documentos_habeis_especiais_ingest_dag.py] Inserindo " + f"{len(documentos_data)} documentos hábeis especiais no " + f"schema transfere_gov" ) db.insert_data( documentos_data, @@ -54,15 +55,17 @@ def fetch_and_store_documentos_habeis_especiais() -> None: ) logging.info( - f"[documentos_habeis_especiais_ingest_dag.py] Concluído. Total de " - f"{len(documentos_data)} documentos hábeis especiais inseridos/atualizados" + f"[documentos_habeis_especiais_ingest_dag.py] Concluído. " + f"Total de {len(documentos_data)} documentos hábeis especiais " + f"inseridos/atualizados" ) else: logging.warning( - "[documentos_habeis_especiais_ingest_dag.py] Nenhum documento hábil " \ + "[documentos_habeis_especiais_ingest_dag.py] Nenhum documento hábil " "especial encontrado" ) fetch_and_store_documentos_habeis_especiais() + dag_instance = api_documentos_habeis_especiais_dag() diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/metas_especiais_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/metas_especiais_ingest_dag.py new file mode 100644 index 00000000..00018b02 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/metas_especiais_ingest_dag.py @@ -0,0 +1,61 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule +from postgres_helpers import get_postgres_conn +from cliente_transferegov_emendas import ClienteTransfereGov +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval=get_dynamic_schedule("metas_especiais_ingest_dag"), + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Tiago", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["transfere_gov_api", "metas_especiais"], +) +def api_metas_especiais_dag() -> None: + """DAG para buscar e armazenar metas especiais do Transfere Gov.""" + + @task + def fetch_and_store_metas_especiais() -> None: + logging.info( + "[metas_especiais_ingest_dag.py] Iniciando extração de metas especiais" + ) + + api = ClienteTransfereGov() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + metas_data = api.get_all_metas_especiais() + + if not metas_data: + logging.warning( + "[metas_especiais_ingest_dag.py] Nenhuma meta especial encontrada" + ) + return + + for meta in metas_data: + meta["dt_ingest"] = datetime.now().isoformat() + + db.insert_data( + metas_data, + "metas_especiais", + conflict_fields=["id_meta"], + primary_key=["id_meta"], + schema="transferegov_emendas", + ) + + logging.info( + f"[metas_especiais_ingest_dag.py] Concluído. " + f"Total: {len(metas_data)} metas especiais inseridas/atualizadas" + ) + + fetch_and_store_metas_especiais() + + +api_metas_especiais_dag() diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/planos_acao_especiais_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/planos_acao_especiais_ingest_dag.py index c8917578..150ccdd4 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/planos_acao_especiais_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/planos_acao_especiais_ingest_dag.py @@ -33,7 +33,9 @@ def fetch_and_store_planos_acao_especiais() -> None: db = ClientPostgresDB(postgres_conn_str) # Buscar IDs dos programas especiais - query = "SELECT DISTINCT id_programa FROM transferegov_emendas.programas_especiais" + query = ( + "SELECT DISTINCT id_programa FROM transferegov_emendas.programas_especiais" + ) programas_ids = db.execute_query(query) if not programas_ids: diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/reltorio_gestao_ingest.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/reltorio_gestao_ingest.py index 1786e38b..90ebce80 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/reltorio_gestao_ingest.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/reltorio_gestao_ingest.py @@ -1,6 +1,6 @@ import logging from airflow.decorators import dag, task -from datetime import datetime, timedelta +from datetime import datetime from typing import Iterator # Imports dos seus módulos personalizados @@ -18,6 +18,44 @@ def chunk_list(lst: list, size: int) -> Iterator[list]: yield lst[i : i + size] +def _fetch_relatorios_for_plano( + api: ClienteTransfereGov, plano_id: int, timestamp: str +) -> list: + """Busca relatórios de um plano e adiciona metadados.""" + logging.info(f"[relatorio_gestao] Buscando relatórios do plano {plano_id}") + + relatorios = api.get_all_relatorio_gestao_especial_by_plano_acao(plano_id) + + if not relatorios: + return [] + + # Adiciona metadados aos registros + for item in relatorios: + # Garante que o ID do plano está no registro + # (caso a API não retorne no corpo) + item["id_plano_acao"] = plano_id + item["dt_ingest"] = timestamp + + return relatorios + + +def _remove_duplicates(all_relatorios: list) -> list: + """Remove duplicatas usando id_relatorio_gestao como chave.""" + unique = {} + for row in all_relatorios: + # Baseado na imagem do Swagger, a PK parece ser + # id_relatorio_gestao. Se quiser garantir unicidade por plano, + # pode usar tupla (id_plano_acao, id_relatorio_gestao) + key = row.get("id_relatorio_gestao") + if key: + unique[key] = row + else: + # Fallback caso venha sem ID (improvável, mas seguro) + logging.warning(f"Registro sem id_relatorio_gestao encontrado: {row}") + + return list(unique.values()) + + @dag( schedule_interval="@daily", start_date=datetime(2023, 1, 1), @@ -38,15 +76,14 @@ def setup_table() -> None: Evita race conditions nas tasks paralelas. """ logging.info("[relatorio_gestao] Configurando tabela...") - + db = ClientPostgresDB(get_postgres_conn()) - + # Remove a tabela antiga se existir (para garantir estrutura correta) db.drop_table_if_exists( - table_name="relatorio_gestao_especial", - schema="transferegov_emendas" + table_name="relatorio_gestao_especial", schema="transferegov_emendas" ) - + # Cria tabela com TODOS os campos que virão da API # Baseado na estrutura real retornada pela API TransfereGov sample_data = { @@ -56,14 +93,14 @@ def setup_table() -> None: "id_plano_acao": "0", "dt_ingest": datetime.now().isoformat(), } - + db.create_table_if_not_exists( sample_data, table_name="relatorio_gestao_especial", primary_key=["id_relatorio_gestao"], schema="transferegov_emendas", ) - + logging.info("[relatorio_gestao] Tabela configurada com sucesso") @task @@ -111,21 +148,7 @@ def process_chunk(chunk: list) -> None: # Processa cada plano de ação pertencente a este chunk for plano_id in chunk: - logging.info(f"[relatorio_gestao] Buscando relatórios do plano {plano_id}") - - # Chama o novo método criado no cliente - relatorios = api.get_all_relatorio_gestao_especial_by_plano_acao(plano_id) - - if not relatorios: - # Não há relatórios para este plano -> pula - continue - - # Adiciona metadados aos registros - for item in relatorios: - # Garante que o ID do plano está no registro (caso a API não retorne no corpo) - item["id_plano_acao"] = plano_id - item["dt_ingest"] = timestamp - + relatorios = _fetch_relatorios_for_plano(api, plano_id, timestamp) all_relatorios.extend(relatorios) if not all_relatorios: @@ -136,22 +159,10 @@ def process_chunk(chunk: list) -> None: f"[relatorio_gestao] Inserindo {len(all_relatorios)} registros no Postgres." ) - # Remove duplicatas locais usando a PK (id_relatorio_gestao) - unique = {} - for row in all_relatorios: - # Baseado na imagem do Swagger, a PK parece ser id_relatorio_gestao - # Se quiser garantir unicidade por plano, pode usar tupla (id_plano_acao, id_relatorio_gestao) - key = row.get("id_relatorio_gestao") - if key: - unique[key] = row - else: - # Fallback caso venha sem ID (improvável, mas seguro) - logging.warning(f"Registro sem id_relatorio_gestao encontrado: {row}") - - all_relatorios_unique = list(unique.values()) + all_relatorios_unique = _remove_duplicates(all_relatorios) if not all_relatorios_unique: - return + return # Insere com UPSERT no Postgres db.insert_data( @@ -166,11 +177,11 @@ def process_chunk(chunk: list) -> None: setup = setup_table() # Cria a tabela primeiro ids = fetch_planos_acao() # Busca todos os planos de ação chunks = split_chunks(ids) # Divide em várias listas menores - + # Garante que a tabela é criada antes do processamento paralelo setup >> ids process_chunk.expand(chunk=chunks) # Executa cada chunk em tasks paralelas # Instancia a DAG -dag_instance = api_relatorio_gestao_especial_dag() \ No newline at end of file +dag_instance = api_relatorio_gestao_especial_dag() diff --git a/airflow_lappis/helpers/safe_request.py b/airflow_lappis/helpers/safe_request.py index 78dceafd..f4985119 100644 --- a/airflow_lappis/helpers/safe_request.py +++ b/airflow_lappis/helpers/safe_request.py @@ -76,4 +76,4 @@ def request_safe( return HTTPStatus.SERVICE_UNAVAILABLE, f"request_error: {e}" # Fallback: não deveria chegar aqui, mas para satisfazer MyPy - return HTTPStatus.INTERNAL_SERVER_ERROR, "unexpected_error" \ No newline at end of file + return HTTPStatus.INTERNAL_SERVER_ERROR, "unexpected_error" diff --git a/airflow_lappis/plugins/cliente_pncp.py b/airflow_lappis/plugins/cliente_pncp.py index 86a9c6fe..57629210 100644 --- a/airflow_lappis/plugins/cliente_pncp.py +++ b/airflow_lappis/plugins/cliente_pncp.py @@ -503,4 +503,4 @@ def get_itens_e_resultados( sequencial, ) - return itens_total, resultados_total \ No newline at end of file + return itens_total, resultados_total diff --git a/airflow_lappis/plugins/cliente_transferegov_emendas.py b/airflow_lappis/plugins/cliente_transferegov_emendas.py index 4c06c8f9..efd20d4a 100644 --- a/airflow_lappis/plugins/cliente_transferegov_emendas.py +++ b/airflow_lappis/plugins/cliente_transferegov_emendas.py @@ -331,7 +331,6 @@ def get_all_empenhos_especiais_by_plano_acao( ) return all_data - def get_relatorio_gestao_especial_by_plano_acao( self, id_plano_acao: int, limit: int = 1000, offset: int = 0 ) -> Optional[list]: @@ -375,8 +374,8 @@ def get_all_relatorio_gestao_especial_by_plano_acao( page = 1 logging.info( - f"[cliente_transfere_gov.py] Starting extraction of relatorio_gestao_especial " - f"for plano_acao={id_plano_acao}" + f"[cliente_transfere_gov.py] Starting extraction of " + f"relatorio_gestao_especial for plano_acao={id_plano_acao}" ) while True: @@ -465,7 +464,8 @@ def get_all_documentos_habeis_especiais(self, page_size: int = 1000) -> list: page = 1 logging.info( - "[cliente_transfere_gov.py] Starting full extraction of documentos hábeis especiais" + "[cliente_transfere_gov.py] Starting full extraction of " + "documentos hábeis especiais" ) while True: @@ -536,8 +536,9 @@ def get_documentos_habeis_especiais_by_empenho( return data else: logging.warning( - f"[cliente_transfere_gov.py] Failed to fetch documentos hábeis especiais for " - f"empenho {id_empenho} with status: {status}" + f"[cliente_transfere_gov.py] Failed to fetch documentos " + f"hábeis especiais for empenho {id_empenho} with status: " + f"{status}" ) return None @@ -576,4 +577,96 @@ def get_all_documentos_habeis_especiais_by_empenho( f"[cliente_transfere_gov.py] Total documentos hábeis especiais for empenho " f"{id_empenho}: {len(all_data)}" ) - return all_data \ No newline at end of file + return all_data + + def get_metas_especiais(self, limit: int = 1000, offset: int = 0) -> Optional[list]: + """ + Obter metas especiais com paginação. + + Args: + limit (int): Quantidade de registros por página (padrão: 1000) + offset (int): Deslocamento inicial (padrão: 0) + + Returns: + list: lista de metas especiais ou None se falhar + """ + endpoint = "meta_especial" + params = { + "select": "*", + "order": "id_meta.asc", + "limit": limit, + "offset": offset, + } + + logging.info( + f"[cliente_transfere_gov.py] Fetching metas especiais with " + f"limit={limit}, offset={offset}" + ) + + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER, params=params + ) + + if status == http.HTTPStatus.OK and isinstance(data, list): + logging.info( + f"[cliente_transfere_gov.py] Successfully fetched {len(data)} " + "metas especiais" + ) + return data + else: + logging.warning( + f"[cliente_transfere_gov.py] Failed to fetch metas especiais " + f"with status: {status}" + ) + return None + + def get_all_metas_especiais(self, page_size: int = 1000) -> list: + """ + Obter todas as metas especiais com paginação automática. + + Args: + page_size (int): Quantidade de registros por requisição (padrão: 1000) + + Returns: + list: lista completa de metas especiais + """ + all_data = [] + offset = 0 + page = 1 + + logging.info( + "[cliente_transfere_gov.py] Starting full extraction of metas especiais" + ) + + while True: + logging.info( + f"[cliente_transfere_gov.py] Fetching page {page} (offset: {offset})" + ) + + data = self.get_metas_especiais(limit=page_size, offset=offset) + + if not data or len(data) == 0: + logging.info( + "[cliente_transfere_gov.py] No more data received. " + "Extraction complete." + ) + break + + all_data.extend(data) + logging.info( + f"[cliente_transfere_gov.py] Page {page} fetched: {len(data)} records. " + f"Total so far: {len(all_data)}" + ) + + if len(data) < page_size: + logging.info("[cliente_transfere_gov.py] Last page reached.") + break + + offset += page_size + page += 1 + + logging.info( + f"[cliente_transfere_gov.py] Extraction completed. " + f"Total metas especiais: {len(all_data)}" + ) + return all_data From bdc0e30ed68a5c71ad61a7dc11a163edcde53e70 Mon Sep 17 00:00:00 2001 From: Tiago Bittencourt Date: Wed, 4 Feb 2026 16:31:51 -0300 Subject: [PATCH 192/317] =?UTF-8?q?feat(dag):=20extra=C3=A7=C3=A3o=20pagin?= =?UTF-8?q?ada=20para=20=20efici=C3=AAcia=20(#61)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../empenhos_especiais_ingest_dag.py | 63 ++++++------- .../plugins/cliente_transferegov_emendas.py | 92 +++++++++++++++++++ 2 files changed, 119 insertions(+), 36 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/empenhos_especiais_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/empenhos_especiais_ingest_dag.py index 3b742226..2213b4de 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/empenhos_especiais_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/empenhos_especiais_ingest_dag.py @@ -12,7 +12,7 @@ start_date=datetime(2023, 1, 1), catchup=False, default_args={ - "owner": "Leonardo", + "owner": "Leonardo e Tiago", "retries": 1, "retry_delay": timedelta(minutes=5), }, @@ -32,47 +32,38 @@ def fetch_and_store_empenhos_especiais() -> None: postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) - # Buscar IDs dos planos de ação especiais - query = ( - "SELECT DISTINCT id_plano_acao " - "FROM transferegov_emendas.planos_acao_especiais" - ) - planos_acao_ids = db.execute_query(query) + # Busca todos os documentos hábeis especiais com paginação automática + documentos_data = api.get_all_empenhos_especiais(page_size=1000) - if not planos_acao_ids: - logging.warning( - "[empenhos_especiais_ingest_dag.py] Nenhum plano de ação encontrado" - ) - return + if documentos_data and len(documentos_data) > 0: + # Adicionar dt_ingest a cada documento + for documento in documentos_data: + documento["dt_ingest"] = datetime.now().isoformat() - total_empenhos = 0 - for (id_plano_acao,) in planos_acao_ids: + # Inserir/atualizar dados no banco logging.info( - f"[empenhos_especiais_ingest_dag.py] Buscando empenhos especiais " - f"para plano de ação {id_plano_acao}" + f"[empenhos_especiais_ingest_dag.py] Inserindo {len(documentos_data)} " + "empenhos especiais no schema transfere_gov" + ) + db.insert_data( + documentos_data, + "empenhos_especiais", + conflict_fields=["id_empenho"], + primary_key=["id_empenho"], + schema="transferegov_emendas", ) - empenhos_data = api.get_all_empenhos_especiais_by_plano_acao(id_plano_acao) - - if empenhos_data: - for empenho in empenhos_data: - empenho["dt_ingest"] = datetime.now().isoformat() - - db.insert_data( - empenhos_data, - "empenhos_especiais", - conflict_fields=["id_empenho"], - primary_key=["id_empenho"], - schema="transferegov_emendas", - ) - total_empenhos += len(empenhos_data) - - logging.info( - f"[empenhos_especiais_ingest_dag.py] Concluído. " - f"Total: {total_empenhos} empenhos especiais inseridos/atualizados" - ) + logging.info( + f"[empenhos_especiais_ingest_dag.py] Concluído. Total de " + f"{len(documentos_data)} empenhos especiais inseridos/atualizados" + ) + else: + logging.warning( + "[empenhos_especiais_ingest_dag.py] Nenhum empenho " \ + "especial encontrado" + ) fetch_and_store_empenhos_especiais() -dag_instance = api_empenhos_especiais_dag() +dag_instance = api_empenhos_especiais_dag() \ No newline at end of file diff --git a/airflow_lappis/plugins/cliente_transferegov_emendas.py b/airflow_lappis/plugins/cliente_transferegov_emendas.py index efd20d4a..1e53ccb4 100644 --- a/airflow_lappis/plugins/cliente_transferegov_emendas.py +++ b/airflow_lappis/plugins/cliente_transferegov_emendas.py @@ -294,6 +294,98 @@ def get_empenhos_especiais_by_plano_acao( ) return None + def get_empenhos_especiais( + self, limit: int = 1000, offset: int = 0 + ) -> Optional[list]: + """ + Obter empenhos especiais com paginação. + Args: + limit (int): Quantidade de registros por página (padrão: 1000) + offset (int): Deslocamento inicial (padrão: 0) + Returns: + list: lista de empenhos especiais ou None se falhar + """ + endpoint = "empenho_especial" + params = { + "select": "*", + "order": "id_empenho.asc", + "limit": limit, + "offset": offset, + } + + logging.info( + f"[cliente_transfere_gov.py] Fetching empenhos especiais with " + f"limit={limit}, offset={offset}" + ) + + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER, params=params + ) + + if status == http.HTTPStatus.OK and isinstance(data, list): + logging.info( + f"[cliente_transfere_gov.py] Successfully fetched {len(data)} " + "empenhos especiais" + ) + return data + else: + logging.warning( + f"[cliente_transfere_gov.py] Failed to fetch empenhos especiais " + f"with status: {status}" + ) + return None + + + def get_all_empenhos_especiais(self, page_size: int = 1000) -> list: + """ + Obter todos os empenhos especiais com paginação automática. + Args: + page_size (int): Quantidade de registros por requisição (padrão: 1000) + Returns: + list: lista completa de empenhos especiais + """ + all_data = [] + offset = 0 + page = 1 + + logging.info( + "[cliente_transfere_gov.py] Starting full extraction of empenhos especiais" + ) + + while True: + logging.info( + f"[cliente_transfere_gov.py] Fetching page {page} " f"(offset: {offset})" + ) + + data = self.get_empenhos_especiais(limit=page_size, offset=offset) + + if not data or len(data) == 0: + logging.info( + "[cliente_transfere_gov.py] No more data received. " + "Extraction complete." + ) + break + + all_data.extend(data) + logging.info( + f"[cliente_transfere_gov.py] Page {page} fetched: {len(data)} records. " + f"Total so far: {len(all_data)}" + ) + + # Se recebemos menos registros que o limite, é a última página + if len(data) < page_size: + logging.info("[cliente_transfere_gov.py] Last page reached.") + break + + offset += page_size + page += 1 + + logging.info( + f"[cliente_transfere_gov.py] Extraction completed. " + f"Total records: {len(all_data)}" + ) + return all_data + def get_all_empenhos_especiais_by_plano_acao( self, id_plano_acao: int, page_size: int = 1000 ) -> list: From b9604e95475735fd7fcb1d57f26326ea13c64be2 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Sat, 14 Feb 2026 18:01:42 -0300 Subject: [PATCH 193/317] feat: nova dag relatorios gestao novo --- ...latorio_gestao_novo_especial_ingest_dag.py | 71 ++++++++++++++ .../plugins/cliente_transferegov_emendas.py | 94 +++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/transferegov_emendas/relatorio_gestao_novo_especial_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/relatorio_gestao_novo_especial_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/relatorio_gestao_novo_especial_ingest_dag.py new file mode 100644 index 00000000..918161a4 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/relatorio_gestao_novo_especial_ingest_dag.py @@ -0,0 +1,71 @@ +import logging +from schedule_loader import get_dynamic_schedule +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from postgres_helpers import get_postgres_conn +from cliente_transferegov_emendas import ClienteTransfereGov +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval=get_dynamic_schedule("relatorio_gestao_novo_especial_dag"), + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Tiago", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["transfere_gov_api", "relatorio_gestao_novo_especial"], +) +def api_relatorio_gestao_novo_especial_dag() -> None: + """DAG para buscar e armazenar relatórios de gestão novo especial do Transfere Gov""" + + @task + def fetch_and_store_relatorios_gestao_novo_especial() -> None: + logging.info( + "[relatorio_gestao_novo_especial_dag.py] Iniciando extração relatórios de" + " gestão novo especial" + ) + + api = ClienteTransfereGov() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + # Busca todos os relatórios de gestão novo especial com paginação automática + relatorios_data = api.get_all_relatorios_gestao_novo_especial(page_size=1000) + + if relatorios_data and len(relatorios_data) > 0: + # Adicionar dt_ingest a cada relatório + for relatorio in relatorios_data: + relatorio["dt_ingest"] = datetime.now().isoformat() + + # Inserir/atualizar dados no banco + logging.info( + f"[relatorio_gestao_novo_especial_dag.py] Inserindo " + f"{len(relatorios_data)} relatórios de gestão no " + f"schema transfere_gov" + ) + db.insert_data( + relatorios_data, + "relatorios_gestao_novo_especial", + conflict_fields=["id_relatorio_gestao_novo"], + primary_key=["id_relatorio_gestao_novo"], + schema="transferegov_emendas", + ) + + logging.info( + f"[relatorio_gestao_novo_especial_dag.py] Concluído. " + f"Total de {len(relatorios_data)} relatórios de gestão novo especial " + f"inseridos/atualizados" + ) + else: + logging.info( + "[relatorio_gestao_novo_especial_dag.py] Nenhum relatório de gestão novo " + "especial encontrado" + ) + + fetch_and_store_relatorios_gestao_novo_especial() + + +api_relatorio_gestao_novo_especial_dag() diff --git a/airflow_lappis/plugins/cliente_transferegov_emendas.py b/airflow_lappis/plugins/cliente_transferegov_emendas.py index 1e53ccb4..f5bb251f 100644 --- a/airflow_lappis/plugins/cliente_transferegov_emendas.py +++ b/airflow_lappis/plugins/cliente_transferegov_emendas.py @@ -762,3 +762,97 @@ def get_all_metas_especiais(self, page_size: int = 1000) -> list: f"Total metas especiais: {len(all_data)}" ) return all_data + + def get_relatorio_gestao_novo_especial(self, limit: int = 1000, offset: int = 0) -> Optional[list]: + """ + Obter relatórios de gestão novo especial com paginação. + + Args: + limit (int): Quantidade de registros por página (padrão: 1000) + offset (int): Deslocamento inicial (padrão: 0) + + Returns: + list: lista de relatórios de gestão novo especial ou None se falhar + """ + endpoint = "relatorio_gestao_novo_especial" + params = { + "select": "*", + "order": "id_relatorio_gestao_novo.asc", + "limit": limit, + "offset": offset, + } + + logging.info( + f"[cliente_transfere_gov.py] Fetching relatórios de gestão novo with " + f"limit={limit}, offset={offset}" + ) + + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER, params=params + ) + + if status == http.HTTPStatus.OK and isinstance(data, list): + logging.info( + f"[cliente_transfere_gov.py] Successfully fetched {len(data)} " + "relatórios de gestão novo" + ) + return data + else: + logging.warning( + f"[cliente_transfere_gov.py] Failed to fetch relatórios de gestão novo " + f"with status: {status}" + ) + return None + + def get_all_relatorios_gestao_novo_especial(self, page_size: int = 1000) -> list: + """ + Obter todos os relatórios de gestão novo especial com paginação automática. + + Args: + page_size (int): Quantidade de registros por requisição (padrão: 1000) + + Returns: + list: lista completa de relatórios de gestão novo especial + """ + all_data = [] + offset = 0 + page = 1 + + logging.info( + "[cliente_transfere_gov.py] Starting full extraction of " + "relatórios de gestão novo" + ) + + while True: + logging.info( + f"[cliente_transfere_gov.py] Fetching page {page} " f"(offset: {offset})" + ) + + data = self.get_relatorio_gestao_novo_especial(limit=page_size, offset=offset) + + if not data or len(data) == 0: + logging.info( + "[cliente_transfere_gov.py] No more data received. " + "Extraction complete." + ) + break + + all_data.extend(data) + logging.info( + f"[cliente_transfere_gov.py] Page {page} fetched: {len(data)} records. " + f"Total so far: {len(all_data)}" + ) + + # Se recebemos menos registros que o limite, é a última página + if len(data) < page_size: + logging.info("[cliente_transfere_gov.py] Last page reached.") + break + + offset += page_size + page += 1 + + logging.info( + f"[cliente_transfere_gov.py] Extraction completed. " + f"Total records: {len(all_data)}" + ) + return all_data \ No newline at end of file From b3aab078e1d6befcbcd3dfe8e449753114f9001b Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Wed, 25 Feb 2026 22:08:17 -0300 Subject: [PATCH 194/317] =?UTF-8?q?feat:=20dag=20extra=C3=A7=C3=A3o=20orde?= =?UTF-8?q?m=20pagamento=20e=20banc=C3=A1ria?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ordem_bancaria_especial_ingest_dag.py | 69 +++++++++++++ .../plugins/cliente_transferegov_emendas.py | 96 +++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/transferegov_emendas/ordem_bancaria_especial_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/ordem_bancaria_especial_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/ordem_bancaria_especial_ingest_dag.py new file mode 100644 index 00000000..11918570 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/ordem_bancaria_especial_ingest_dag.py @@ -0,0 +1,69 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule +from postgres_helpers import get_postgres_conn +from cliente_transferegov_emendas import ClienteTransfereGov +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval=get_dynamic_schedule("ordem_bancaria_especial_ingest_dag"), + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Tiago", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["transfere_gov_api", "ordem_bancaria_especial"], +) +def api_ordem_bancaria_especial_dag() -> None: + """DAG para buscar e armazenar ordens bancárias especiais do Transfere Gov.""" + + @task + def fetch_and_store_ordem_bancaria_especial() -> None: + logging.info( + "[ordem_bancaria_especial_ingest_dag.py] Iniciando extração ordens bancárias especiais" + ) + + api = ClienteTransfereGov() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + # Busca todas as ordens bancárias especiais com paginação automática + ordem_data = api.get_all_ordens_bancarias_especiais(page_size=1000) + + if ordem_data and len(ordem_data) > 0: + # Adicionar dt_ingest a cada documento + for documento in ordem_data: + documento["dt_ingest"] = datetime.now().isoformat() + + # Inserir/atualizar dados no banco + logging.info( + f"[ordem_bancaria_especial_ingest_dag.py] Inserindo " + f"{len(ordem_data)} ordens bancárias especiais no " + f"schema transfere_gov" + ) + db.insert_data( + ordem_data, + "ordens_bancarias_especiais", + conflict_fields=["id_op_ob"], + primary_key=["id_op_ob"], + schema="transferegov_emendas", + ) + + logging.info( + f"[ordem_bancaria_especial_ingest_dag.py] Concluído. " + f"Total de {len(ordem_data)} ordens bancárias especiais " + f"inseridas/atualizadas" + ) + else: + logging.warning( + "[ordem_bancaria_especial_ingest_dag.py] Nenhuma ordem bancária especial " + "encontrada" + ) + + fetch_and_store_ordem_bancaria_especial() + +api_ordem_bancaria_especial_dag() diff --git a/airflow_lappis/plugins/cliente_transferegov_emendas.py b/airflow_lappis/plugins/cliente_transferegov_emendas.py index 1e53ccb4..ff0f1621 100644 --- a/airflow_lappis/plugins/cliente_transferegov_emendas.py +++ b/airflow_lappis/plugins/cliente_transferegov_emendas.py @@ -762,3 +762,99 @@ def get_all_metas_especiais(self, page_size: int = 1000) -> list: f"Total metas especiais: {len(all_data)}" ) return all_data + + def get_ordens_bancarias_especiais( + self, limit: int = 1000, offset: int = 0 + ) -> Optional[list]: + """ + Obter ordens bancárias especiais com paginação. + + Args: + limit (int): Quantidade de registros por página (padrão: 1000) + offset (int): Deslocamento inicial (padrão: 0) + + Returns: + list: lista de ordens bancárias especiais ou None se falhar + """ + endpoint = "ordem_pagamento_ordem_bancaria_especial" + params = { + "select": "*", + "order": "id_op_ob.asc", + "limit": limit, + "offset": offset, + } + + logging.info( + f"[cliente_transfere_gov.py] Fetching ordens bancárias especiais with " + f"limit={limit}, offset={offset}" + ) + + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER, params=params + ) + + if status == http.HTTPStatus.OK and isinstance(data, list): + logging.info( + f"[cliente_transfere_gov.py] Successfully fetched {len(data)} " + "ordens bancárias especiais" + ) + return data + else: + logging.warning( + f"[cliente_transfere_gov.py] Failed to fetch ordens bancárias especiais " + f"with status: {status}" + ) + return None + + def get_all_ordens_bancarias_especiais(self, page_size: int = 1000) -> list: + """ + Obter todas as ordens bancárias especiais com paginação automática. + + Args: + page_size (int): Quantidade de registros por requisição (padrão: 1000) + + Returns: + list: lista completa de ordens bancárias especiais + """ + all_data = [] + offset = 0 + page = 1 + + logging.info( + "[cliente_transfere_gov.py] Starting full extraction of " + "ordens bancárias especiais" + ) + + while True: + logging.info( + f"[cliente_transfere_gov.py] Fetching page {page} " f"(offset: {offset})" + ) + + data = self.get_ordens_bancarias_especiais(limit=page_size, offset=offset) + + if not data or len(data) == 0: + logging.info( + "[cliente_transfere_gov.py] No more data received. " + "Extraction complete." + ) + break + + all_data.extend(data) + logging.info( + f"[cliente_transfere_gov.py] Page {page} fetched: {len(data)} records. " + f"Total so far: {len(all_data)}" + ) + + # Se recebemos menos registros que o limite, é a última página + if len(data) < page_size: + logging.info("[cliente_transfere_gov.py] Last page reached.") + break + + offset += page_size + page += 1 + + logging.info( + f"[cliente_transfere_gov.py] Extraction completed. " + f"Total records: {len(all_data)}" + ) + return all_data From 5e0250288b3b03e317d5a1f555ecfa34e7e329f6 Mon Sep 17 00:00:00 2001 From: Mateushqms Date: Thu, 26 Feb 2026 00:02:19 -0300 Subject: [PATCH 195/317] feat: dag ingestao plano de trabalho --- .../plano_trabalho_especial_ingest_dag.py | 67 +++++++++++++ .../plugins/cliente_transferegov_emendas.py | 98 ++++++++++++++++++- 2 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 airflow_lappis/dags/data_ingest/transferegov_emendas/plano_trabalho_especial_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/plano_trabalho_especial_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/plano_trabalho_especial_ingest_dag.py new file mode 100644 index 00000000..f7e08893 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/plano_trabalho_especial_ingest_dag.py @@ -0,0 +1,67 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule +from postgres_helpers import get_postgres_conn +from cliente_transferegov_emendas import ClienteTransfereGov +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval=get_dynamic_schedule("plano_trabalho_especial_ingest_dag"), + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Mateus", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["transfere_gov_api", "plano_trabalho"], +) +def api_plano_trabalho_especial_dag() -> None: + """DAG para buscar e armazenar planos de trabalho especiais do Transfere Gov.""" + + @task + def fetch_and_store_plano_trabalho_especial() -> None: + logging.info( + "[plano_trabalho_especial_ingest_dag.py] Iniciando extração de " + "planos de trabalho especiais" + ) + + api = ClienteTransfereGov() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + plano_data = api.get_all_plano_trabalho_especial(page_size=1000) + + if plano_data and len(plano_data) > 0: + for item in plano_data: + item["dt_ingest"] = datetime.now().isoformat() + + logging.info( + f"[plano_trabalho_especial_ingest_dag.py] Inserindo " + f"{len(plano_data)} planos de trabalho no schema transferegov_emendas" + ) + + db.insert_data( + plano_data, + "plano_trabalho_especial", + conflict_fields=["id_plano_trabalho"], + primary_key=["id_plano_trabalho"], + schema="transferegov_emendas", + ) + + logging.info( + f"[plano_trabalho_especial_ingest_dag.py] Concluído. " + f"Total de {len(plano_data)} registros processados." + ) + else: + logging.warning( + "[plano_trabalho_especial_ingest_dag.py] Nenhum plano de trabalho " + "encontrado" + ) + + fetch_and_store_plano_trabalho_especial() + + +dag_instance = api_plano_trabalho_especial_dag() diff --git a/airflow_lappis/plugins/cliente_transferegov_emendas.py b/airflow_lappis/plugins/cliente_transferegov_emendas.py index 1e53ccb4..3eae9078 100644 --- a/airflow_lappis/plugins/cliente_transferegov_emendas.py +++ b/airflow_lappis/plugins/cliente_transferegov_emendas.py @@ -335,7 +335,6 @@ def get_empenhos_especiais( ) return None - def get_all_empenhos_especiais(self, page_size: int = 1000) -> list: """ Obter todos os empenhos especiais com paginação automática. @@ -355,7 +354,7 @@ def get_all_empenhos_especiais(self, page_size: int = 1000) -> list: while True: logging.info( f"[cliente_transfere_gov.py] Fetching page {page} " f"(offset: {offset})" - ) + ) data = self.get_empenhos_especiais(limit=page_size, offset=offset) @@ -762,3 +761,98 @@ def get_all_metas_especiais(self, page_size: int = 1000) -> list: f"Total metas especiais: {len(all_data)}" ) return all_data + + def get_plano_trabalho_especial( + self, limit: int = 1000, offset: int = 0 + ) -> Optional[list]: + """ + Obter planos de trabalho especiais com paginação. + + Args: + limit (int): Quantidade de registros por página (padrão: 1000) + offset (int): Deslocamento inicial (padrão: 0) + + Returns: + list: lista de planos de trabalho ou None se falhar + """ + endpoint = "plano_trabalho_especial" + params = { + "select": "*", + "order": "id_plano_trabalho.asc", + "limit": limit, + "offset": offset, + } + + logging.info( + f"[cliente_transfere_gov.py] Fetching plano_trabalho_especial with " + f"limit={limit}, offset={offset}" + ) + + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER, params=params + ) + + if status == http.HTTPStatus.OK and isinstance(data, list): + logging.info( + f"[cliente_transfere_gov.py] Successfully fetched {len(data)} " + "planos de trabalho especiais" + ) + return data + else: + logging.warning( + f"[cliente_transfere_gov.py] Failed to fetch plano_trabalho_especial " + f"with status: {status}" + ) + return None + + def get_all_plano_trabalho_especial(self, page_size: int = 1000) -> list: + """ + Obter todos os planos de trabalho especiais com paginação automática. + + Args: + page_size (int): Quantidade de registros por requisição (padrão: 1000) + + Returns: + list: lista completa de planos de trabalho especiais + """ + all_data = [] + offset = 0 + page = 1 + + logging.info( + "[cliente_transfere_gov.py] Starting full extraction of " + "plano_trabalho_especial" + ) + + while True: + logging.info( + f"[cliente_transfere_gov.py] Fetching page {page} (offset: {offset})" + ) + + data = self.get_plano_trabalho_especial(limit=page_size, offset=offset) + + if not data or len(data) == 0: + logging.info( + "[cliente_transfere_gov.py] No more data received. " + "Extraction complete." + ) + break + + all_data.extend(data) + logging.info( + f"[cliente_transfere_gov.py] Page {page} fetched: {len(data)} records. " + f"Total so far: {len(all_data)}" + ) + + if len(data) < page_size: + logging.info("[cliente_transfere_gov.py] Last page reached.") + break + + offset += page_size + page += 1 + + logging.info( + f"[cliente_transfere_gov.py] Extraction completed. " + f"Total records: {len(all_data)}" + ) + return all_data From 50e81bacd3659e328c6cdd3c483ae22d6782a774 Mon Sep 17 00:00:00 2001 From: Mateushqms Date: Thu, 26 Feb 2026 00:05:38 -0300 Subject: [PATCH 196/317] chore: aplica a formatacao do codigo --- .../transferegov_emendas/empenhos_especiais_ingest_dag.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/empenhos_especiais_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/empenhos_especiais_ingest_dag.py index 2213b4de..e7d40c79 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/empenhos_especiais_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/empenhos_especiais_ingest_dag.py @@ -59,11 +59,10 @@ def fetch_and_store_empenhos_especiais() -> None: ) else: logging.warning( - "[empenhos_especiais_ingest_dag.py] Nenhum empenho " \ - "especial encontrado" + "[empenhos_especiais_ingest_dag.py] Nenhum empenho " "especial encontrado" ) fetch_and_store_empenhos_especiais() -dag_instance = api_empenhos_especiais_dag() \ No newline at end of file +dag_instance = api_empenhos_especiais_dag() From 06c36e277f2f1fa75fd2202fa5f53c04d74ea294 Mon Sep 17 00:00:00 2001 From: Mateushqms Date: Thu, 26 Feb 2026 10:19:53 -0300 Subject: [PATCH 197/317] =?UTF-8?q?refactor:=20atribui=C3=A7=C3=A3o=20desn?= =?UTF-8?q?ecess=C3=A1ria=20de=20dag=5Finstance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transferegov_emendas/plano_trabalho_especial_ingest_dag.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/plano_trabalho_especial_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/plano_trabalho_especial_ingest_dag.py index f7e08893..620a917b 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/plano_trabalho_especial_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/plano_trabalho_especial_ingest_dag.py @@ -64,4 +64,4 @@ def fetch_and_store_plano_trabalho_especial() -> None: fetch_and_store_plano_trabalho_especial() -dag_instance = api_plano_trabalho_especial_dag() +api_plano_trabalho_especial_dag() From e737cba900835736cd8ed265d62fe5389193a29a Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Thu, 26 Feb 2026 17:16:08 -0300 Subject: [PATCH 198/317] =?UTF-8?q?feat:=20extra=C3=A7=C3=A3o=20historico?= =?UTF-8?q?=20pagamento=20emendas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...storico_pagamentos_especiais_ingest_dag.py | 72 ++++++++++++++ .../plugins/cliente_transferegov_emendas.py | 94 +++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/transferegov_emendas/historico_pagamentos_especiais_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/historico_pagamentos_especiais_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/historico_pagamentos_especiais_ingest_dag.py new file mode 100644 index 00000000..574c2b7a --- /dev/null +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/historico_pagamentos_especiais_ingest_dag.py @@ -0,0 +1,72 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule +from postgres_helpers import get_postgres_conn +from cliente_transferegov_emendas import ClienteTransfereGov +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval=get_dynamic_schedule("historico_pagamentos_especiais_ingest_dag"), + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Tiago", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["transfere_gov_api", "historico_pagamentos_especiais"], +) +def api_historico_pagamentos_especiais_dag() -> None: + """DAG para buscar e armazenar histórico de pagamentos especiais do Transfere Gov.""" + + @task + def fetch_and_store_historico_pagamentos_especiais() -> None: + logging.info( + "[historico_pagamentos_especiais_ingest_dag.py] Iniciando extração histórico " + "de pagamentos especiais" + ) + + api = ClienteTransfereGov() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + # Busca todos os documentos hábeis especiais com paginação automática + historico_data = api.get_all_historico_pagamentos_especiais(page_size=1000) + + if historico_data and len(historico_data) > 0: + # Adicionar dt_ingest a cada documento + for documento in historico_data: + documento["dt_ingest"] = datetime.now().isoformat() + + # Inserir/atualizar dados no banco + logging.info( + f"[historico_pagamentos_especiais_ingest_dag.py] Inserindo " + f"{len(historico_data)} registros de histórico de pagamentos especiais no " + f"schema transfere_gov" + ) + db.insert_data( + historico_data, + "historico_pagamentos_especiais", + conflict_fields=["id_historico_op_ob"], + primary_key=["id_historico_op_ob"], + schema="transferegov_emendas", + ) + + logging.info( + f"[historico_pagamentos_especiais_ingest_dag.py] Concluído. " + f"Total de {len(historico_data)} registros de histórico de pagamentos" + " especiais " + f"inseridos/atualizados" + ) + else: + logging.warning( + "[historico_pagamentos_especiais_ingest_dag.py] Nenhum registro de " + "histórico de pagamento especial encontrado" + ) + + fetch_and_store_historico_pagamentos_especiais() + + +api_historico_pagamentos_especiais_dag() diff --git a/airflow_lappis/plugins/cliente_transferegov_emendas.py b/airflow_lappis/plugins/cliente_transferegov_emendas.py index 1e53ccb4..3165e62f 100644 --- a/airflow_lappis/plugins/cliente_transferegov_emendas.py +++ b/airflow_lappis/plugins/cliente_transferegov_emendas.py @@ -762,3 +762,97 @@ def get_all_metas_especiais(self, page_size: int = 1000) -> list: f"Total metas especiais: {len(all_data)}" ) return all_data + + def get_historico_pagamentos_especiais(self, limit: int = 1000, offset: int = 0) -> Optional[list]: + """ + Obter histórico de pagamentos especiais com paginação. + + Args: + limit (int): Quantidade de registros por página (padrão: 1000) + offset (int): Deslocamento inicial (padrão: 0) + + Returns: + list: lista do histórico de pagamentos especiais ou None se falhar + """ + endpoint = "historico_pagamento_especial" + params = { + "select": "*", + "order": "id_historico_op_ob.asc", + "limit": limit, + "offset": offset, + } + + logging.info( + f"[cliente_transfere_gov.py] Fetching histórico de pagamentos especiais with " + f"limit={limit}, offset={offset}" + ) + + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER, params=params + ) + + if status == http.HTTPStatus.OK and isinstance(data, list): + logging.info( + f"[cliente_transfere_gov.py] Successfully fetched {len(data)} " + "histórico de pagamentos especiais" + ) + return data + else: + logging.warning( + f"[cliente_transfere_gov.py] Failed to fetch histórico de pagamentos" + " especiais " + f"with status: {status}" + ) + return None + + def get_all_historico_pagamentos_especiais(self, page_size: int = 1000) -> list: + """ + Obter todo o historico de pagamentos especiais com paginação automática. + + Args: + page_size (int): Quantidade de registros por requisição (padrão: 1000) + + Returns: + list: lista completa do histórico de pagamentos especiais + """ + all_data = [] + offset = 0 + page = 1 + + logging.info( + "[cliente_transfere_gov.py] Starting full extraction of histórico de" + " pagamentos especiais" + ) + + while True: + logging.info( + f"[cliente_transfere_gov.py] Fetching page {page} (offset: {offset})" + ) + + data = self.get_historico_pagamentos_especiais(limit=page_size, offset=offset) + + if not data or len(data) == 0: + logging.info( + "[cliente_transfere_gov.py] No more data received. " + "Extraction complete." + ) + break + + all_data.extend(data) + logging.info( + f"[cliente_transfere_gov.py] Page {page} fetched: {len(data)} records. " + f"Total so far: {len(all_data)}" + ) + + if len(data) < page_size: + logging.info("[cliente_transfere_gov.py] Last page reached.") + break + + offset += page_size + page += 1 + + logging.info( + f"[cliente_transfere_gov.py] Extraction completed. " + f"Total histórico de pagamentos especiais: {len(all_data)}" + ) + return all_data From b2f7c6384b17edb95425d1f0bd5b3afc225c1720 Mon Sep 17 00:00:00 2001 From: Tiago Bittencourt Date: Mon, 2 Mar 2026 09:46:19 -0300 Subject: [PATCH 199/317] feat(metadata): nova tabela de metadados (dt-transform) (#62) --- airflow_lappis/dags/dbt/ipea/dbt_project.yml | 3 + .../macros/metadata/generate_metadata.sql | 46 +++++++++++++ .../ipea/models/metadata/models_metadata.sql | 67 +++++++++++++++++++ .../dags/dbt/ipea/models/metadata/schema.yml | 46 +++++++++++++ 4 files changed, 162 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/macros/metadata/generate_metadata.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/metadata/models_metadata.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/metadata/schema.yml diff --git a/airflow_lappis/dags/dbt/ipea/dbt_project.yml b/airflow_lappis/dags/dbt/ipea/dbt_project.yml index fab017ca..7bf6e704 100755 --- a/airflow_lappis/dags/dbt/ipea/dbt_project.yml +++ b/airflow_lappis/dags/dbt/ipea/dbt_project.yml @@ -20,6 +20,9 @@ clean-targets: models: ipea: +database: analytics + metadata: + +materialized: incremental + +schema: metadata contratos_dbt: +materialized: table +schema: contratos diff --git a/airflow_lappis/dags/dbt/ipea/macros/metadata/generate_metadata.sql b/airflow_lappis/dags/dbt/ipea/macros/metadata/generate_metadata.sql new file mode 100644 index 00000000..8bfb115b --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/macros/metadata/generate_metadata.sql @@ -0,0 +1,46 @@ +{% macro get_model_metadata() %} +{# + Esta macro retorna os metadados do modelo atual. + Pode ser usada em post-hooks para registrar metadados automaticamente. +#} + SELECT + '{{ this.schema }}' AS schema_name, + '{{ this.name }}' AS table_name, + '{{ this.database }}' AS database_name, + ('{{ run_started_at }}'::TIMESTAMP AT TIME ZONE 'UTC' AT TIME ZONE 'America/Sao_Paulo') AS dt_transform, + '{{ invocation_id }}' AS run_id +{% endmacro %} + + +{% macro register_model_metadata() %} +{# + Esta macro registra os metadados do modelo em uma tabela central. + Deve ser usada como post-hook nos modelos que deseja rastrear. + + Uso no dbt_project.yml: + models: + ipea: + +post-hook: + - "{{ register_model_metadata() }}" +#} + + INSERT INTO {{ target.database }}.metadata.models_metadata ( + schema_name, + table_name, + database_name, + dt_transform, + run_id + ) + VALUES ( + '{{ this.schema }}', + '{{ this.name }}', + '{{ this.database }}', + ('{{ run_started_at }}'::TIMESTAMP AT TIME ZONE 'UTC' AT TIME ZONE 'America/Sao_Paulo'), + '{{ invocation_id }}' + ) + ON CONFLICT (schema_name, table_name) + DO UPDATE SET + dt_transform = EXCLUDED.dt_transform, + run_id = EXCLUDED.run_id; + +{% endmacro %} diff --git a/airflow_lappis/dags/dbt/ipea/models/metadata/models_metadata.sql b/airflow_lappis/dags/dbt/ipea/models/metadata/models_metadata.sql new file mode 100644 index 00000000..f1981994 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/metadata/models_metadata.sql @@ -0,0 +1,67 @@ +{{ + config( + materialized='incremental', + unique_key=['schema_name', 'table_name'], + on_schema_change='sync_all_columns' + ) +}} + +{# + Tabela de Metadados dos Modelos dbt + =================================== + + Esta tabela armazena metadados de todos os modelos executados no dbt. + + Campos principais: + - schema_name: Schema do modelo + - table_name: Nome da tabela/modelo + - dt_transform: Data da última transformação (quando o modelo foi executado) + - run_id: ID único da execução do dbt + + A tabela é atualizada de forma incremental, mantendo apenas o registro + mais recente para cada combinação de schema + table_name. +#} + +WITH dbt_models AS ( + {# + Usando a função graph do dbt para iterar sobre todos os modelos do projeto. + Isso garante que capturamos metadados de todos os modelos definidos. + #} + {% set models_data = [] %} + + {% for node in graph.nodes.values() %} + {% if node.resource_type == 'model' %} + {% do models_data.append({ + 'schema_name': node.schema, + 'table_name': node.name, + 'database_name': node.database, + 'materialization': node.config.materialized, + 'description': node.description | default('') | replace("'", "''") + }) %} + {% endif %} + {% endfor %} + + {% for model in models_data %} + SELECT + '{{ model.schema_name }}' AS schema_name, + '{{ model.table_name }}' AS table_name, + '{{ model.database_name }}' AS database_name, + '{{ model.materialization }}' AS materialization, + '{{ model.description[:500] }}' AS description, + ('{{ run_started_at }}'::TIMESTAMP AT TIME ZONE 'UTC' AT TIME ZONE 'America/Sao_Paulo') AS dt_transform, + '{{ invocation_id }}' AS run_id + {% if not loop.last %} + UNION ALL + {% endif %} + {% endfor %} +) + +SELECT + schema_name, + table_name, + database_name, + materialization, + description, + dt_transform, + run_id +FROM dbt_models diff --git a/airflow_lappis/dags/dbt/ipea/models/metadata/schema.yml b/airflow_lappis/dags/dbt/ipea/models/metadata/schema.yml new file mode 100644 index 00000000..e4fce0ab --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/metadata/schema.yml @@ -0,0 +1,46 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/dbt-labs/dbt-jsonschema/main/schemas/latest/dbt_yml_files-latest.json + +version: 2 + +models: + - name: models_metadata + description: > + Tabela central de metadados que armazena informações sobre todos os modelos dbt executados. + Cada linha representa um modelo único, identificado pela combinação de schema e table_name. + A tabela é atualizada de forma incremental, mantendo histórico das execuções. + meta: + tags: + - metadata + - governance + columns: + - name: schema_name + description: Nome do schema onde o modelo está localizado. + tests: + - not_null + + - name: table_name + description: Nome da tabela/modelo. + tests: + - not_null + + - name: database_name + description: Nome do banco de dados onde o modelo está materializado. + + - name: materialization + description: Tipo de materialização do modelo (table, view, incremental, etc). + + - name: description + description: Descrição do modelo extraída do schema.yml. + + - name: dt_transform + description: > + Data e hora em que o modelo foi transformado/executado pela última vez. + Corresponde ao momento em que a execução do dbt foi iniciada (run_started_at). + Timezone: America/Sao_Paulo (UTC-3). + tests: + - not_null + + - name: run_id + description: > + Identificador único da execução do dbt (invocation_id). + Permite rastrear qual execução gerou a transformação. From fc208f242ca8db2d57fa04b38e2c309c4fca28a1 Mon Sep 17 00:00:00 2001 From: Tiago Bittencourt Date: Mon, 2 Mar 2026 10:00:00 -0300 Subject: [PATCH 200/317] Passagem da coluna dt_ingest pelos modelos (#63) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(ingest): passagem da coluna dt_ingest pelas camadas * feat(schema): documentação do dt_ingest --- .../models/contratos_dbt/bronze/contratos.sql | 3 +- .../contratos_dbt/bronze/cronogramas.sql | 3 +- .../models/contratos_dbt/bronze/empenhos.sql | 4 +- .../contratos_dbt/bronze/empenhos_tesouro.sql | 3 +- .../models/contratos_dbt/bronze/faturas.sql | 3 +- .../models/contratos_dbt/bronze/schema.yml | 15 +++++ .../gold/contratos_comparativo_mensal.sql | 6 +- .../contratos_dbt/gold/contratos_resumo.sql | 5 +- .../gold/contratos_somatorio.sql | 3 +- .../ipea/models/contratos_dbt/gold/schema.yml | 9 +++ .../silver/contratos_empenhos.sql | 14 ++-- .../silver/contratos_estagios.sql | 12 ++-- .../silver/contratos_faturas.sql | 3 +- .../silver/cronogramas_faturas_mensal.sql | 12 ++-- .../contratos_dbt/silver/estagios_mensal.sql | 18 +++-- .../models/contratos_dbt/silver/schema.yml | 15 +++++ .../bronze/visao_orcamentaria_total.sql | 2 +- .../bronze/afastamento_historico.sql | 9 ++- .../pessoas_dbt/bronze/cargos_funcoes.sql | 6 +- .../pessoas_dbt/bronze/dados_afastamento.sql | 9 ++- .../pessoas_dbt/bronze/dados_curriculo.sql | 6 +- .../pessoas_dbt/bronze/dados_dependentes.sql | 9 ++- .../pessoas_dbt/bronze/dados_escolares.sql | 6 +- .../pessoas_dbt/bronze/dados_financeiros.sql | 12 ++-- .../pessoas_dbt/bronze/dados_funcionais.sql | 3 +- .../models/pessoas_dbt/bronze/dados_pa.sql | 6 +- .../pessoas_dbt/bronze/dados_pessoais.sql | 6 +- .../models/pessoas_dbt/bronze/dados_uorg.sql | 6 +- .../estrutura_organizacional_cargos.sql | 8 ++- .../pessoas_dbt/bronze/lista_servidores.sql | 2 +- .../models/pessoas_dbt/bronze/lista_uorgs.sql | 2 +- .../ipea/models/pessoas_dbt/bronze/schema.yml | 39 +++++++++++ .../pessoas_dbt/bronze/terceirizados.sql | 3 +- .../bronze/unidade_organizacional.sql | 3 +- .../gold/aposentadorias_resumo.sql | 3 +- .../pessoas_dbt/gold/cargos_consolidado.sql | 6 +- .../pessoas_dbt/gold/distribuicao_genero.sql | 3 +- .../pessoas_dbt/gold/distribuicao_mapa_uf.sql | 8 ++- .../gold/distribuicao_raca_cor.sql | 2 +- .../gold/distribuicao_situacao_funcional.sql | 6 +- .../models/pessoas_dbt/gold/hierarquia.sql | 9 ++- .../pessoas_dbt/gold/kpis_servidores.sql | 21 +++--- .../gold/resumo_quadro_pessoal.sql | 3 +- .../ipea/models/pessoas_dbt/gold/schema.yml | 30 +++++++++ .../gold/tabela_servidores_agregada.sql | 8 ++- .../silver/afastamento_consolidado.sql | 9 ++- .../silver/quantitativo_alocados_ocupados.sql | 28 ++++++-- .../ipea/models/pessoas_dbt/silver/schema.yml | 15 +++++ .../silver/servidores_completos.sql | 66 +++++++++++++++++-- .../silver/servidores_detalhados.sql | 14 +++- .../silver/tabela_correlacao_cargos.sql | 24 +++++-- .../unidades_organizacionais_siorg_siape.sql | 12 +++- .../ipea/models/ted_dbt/bronze/nc_tesouro.sql | 3 +- .../ipea/models/ted_dbt/bronze/pf_tesouro.sql | 3 +- .../models/ted_dbt/bronze/planos_acao.sql | 3 +- .../dbt/ipea/models/ted_dbt/bronze/schema.yml | 9 +++ .../dbt/ipea/models/ted_dbt/gold/schema.yml | 3 + .../ted_dbt/gold/ted_resumo_orcamentario.sql | 29 ++++---- .../models/ted_dbt/silver/nc_plano_acao.sql | 3 +- .../models/ted_dbt/silver/pf_plano_acao.sql | 46 +++++++++++-- .../dbt/ipea/models/ted_dbt/silver/schema.yml | 9 +++ 61 files changed, 504 insertions(+), 136 deletions(-) diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/contratos.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/contratos.sql index 6046b96f..8d38922d 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/contratos.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/contratos.sql @@ -114,7 +114,8 @@ with and cast(vigencia_fim as text) ~ '^\d{4}-\d{2}-\d{2}$' -- Retorna NULL se não for uma data válida then to_date(cast(vigencia_fim as text), 'YYYY-MM-DD') - end as vigencia_fim + end as vigencia_fim, + (dt_ingest || '-03:00')::timestamptz as dt_ingest from {{ source("compras_gov", "contratos") }} ) -- diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/cronogramas.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/cronogramas.sql index 4527e8f3..ba0da19f 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/cronogramas.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/cronogramas.sql @@ -13,7 +13,8 @@ with anoref::integer as anoref, retroativo::text as retroativo, replace(replace(valor::text, '.', ''), ',', '.')::numeric(15, 2) as valor, - vencimento::date as vencimento + vencimento::date as vencimento, + (dt_ingest || '-03:00')::timestamptz as dt_ingest from {{ source("compras_gov", "cronograma") }} ), diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos.sql index fa5742b7..1bb6ee31 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos.sql @@ -49,8 +49,8 @@ with and data_emissao::text ~ '^\d{4}-\d{2}-\d{2}$' -- Retorna NULL se não for uma data válida then to_date(data_emissao::text, 'YYYY-MM-DD') - end as data_emissao - + end as data_emissao, + (dt_ingest || '-03:00')::timestamptz as dt_ingest from {{ source("compras_gov", "empenhos") }} ) diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos_tesouro.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos_tesouro.sql index 84329c31..f5efa88f 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos_tesouro.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/empenhos_tesouro.sql @@ -39,7 +39,8 @@ with {{ parse_financial_value("despesas_pagas") }} as despesas_pagas, {{ parse_financial_value("restos_a_pagar_inscritos") }} as restos_a_pagar_inscritos, - {{ parse_financial_value("restos_a_pagar_pagos") }} as restos_a_pagar_pagos + {{ parse_financial_value("restos_a_pagar_pagos") }} as restos_a_pagar_pagos, + (dt_ingest || '-03:00')::timestamptz as dt_ingest from {{ source("siafi", "empenhos_tesouro") }} where ne_ccor_ano_emissao like '20%' ) diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/faturas.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/faturas.sql index 8c7c3cea..0a8b5f88 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/faturas.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/faturas.sql @@ -33,7 +33,8 @@ with dados_item_faturado::text as dados_item_faturado, jsonb_array_elements( replace(dados_empenho, '''', '"')::jsonb - ) as dados_empenho + ) as dados_empenho, + (dt_ingest || '-03:00')::timestamptz as dt_ingest from {{ source("compras_gov", "faturas") }} ), -- Extrai os campos do JSON e transforma em colunas individuais diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/schema.yml b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/schema.yml index d251b2a6..1ef6173e 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/bronze/schema.yml @@ -82,6 +82,9 @@ models: - name: vigencia_fim description: > Data de término da vigência do contrato, validada e convertida para formato de data padrão. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. data_tests: - row_count_match: source_table: compras_gov.contratos @@ -157,6 +160,9 @@ models: - name: vencimento description: > Data de vencimento da parcela do cronograma. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. data_tests: - verificacao_tipagem: nome_tabela: 'contratos.cronogramas' @@ -243,6 +249,9 @@ models: - name: valor_empenho description: > Valor do empenho extraído do campo JSON dados_empenho. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. data_tests: - verificacao_tipagem: nome_tabela: 'contratos.faturas' @@ -352,6 +361,9 @@ models: - name: data_emissao description: > Data de emissão do empenho, validada e convertida para formato de data padrão. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. data_tests: - row_count_match: source_table: compras_gov.empenhos @@ -438,6 +450,9 @@ models: - name: restos_a_pagar_pagos description: > Valor dos restos a pagar pagos, formatado como numérico. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. data_tests: - verificacao_tipagem: nome_tabela: 'contratos.empenhos_tesouro' diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql index eeaa1116..3c192cd3 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql @@ -19,7 +19,8 @@ with s.valor_liquidado as siafi_valor_liquidado, s.valor_pago as siafi_valor_pago, s.restos_a_pagar as siafi_restos_a_pagar, - s.restos_a_pagar_pago as siafi_restos_a_pagar_pago + s.restos_a_pagar_pago as siafi_restos_a_pagar_pago, + c.dt_ingest as dt_ingest from compras_gov_data as c full join siafi_data as s using (contrato_id, mes_ref) @@ -38,6 +39,7 @@ select siafi_valor_liquidado, siafi_valor_pago, siafi_restos_a_pagar, - siafi_restos_a_pagar_pago + siafi_restos_a_pagar_pago, + dt_ingest from partial_result full join preenchimento using (contrato_id, mes_ref) diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_resumo.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_resumo.sql index 9499356c..de5e03c0 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_resumo.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_resumo.sql @@ -1,7 +1,7 @@ with valores_pagos_contratos as ( - select contrato_id as id, sum(despesas_pagas) as despesas_pagas + select contrato_id as id, sum(despesas_pagas) as despesas_pagas, max(dt_ingest) as dt_ingest_vpc from {{ ref("contratos_empenhos") }} where contrato_id is not null group by contrato_id @@ -48,5 +48,6 @@ select when vigencia_fim - vigencia_inicio >= 730 and num_parcelas > 1 then 'Sim' else 'Não' - end as continuado + end as continuado, + greatest(dt_ingest, dt_ingest_vpc) as dt_ingest from contratos_gold diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_somatorio.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_somatorio.sql index d1c913a6..523dd547 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_somatorio.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_somatorio.sql @@ -11,7 +11,8 @@ select sum(siafi_valor_empenhado) as total_empenhado, sum(siafi_valor_liquidado) as total_liquidado, - sum(siafi_valor_pago) as total_pago + sum(siafi_valor_pago) as total_pago, + max(dt_ingest) as dt_ingest from {{ ref("contratos_comparativo_mensal") }} group by contrato_id diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/schema.yml b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/schema.yml index 4315bfd5..c8ce30b4 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/schema.yml @@ -67,6 +67,9 @@ models: - name: continuado description: > Indica se o contrato é de prestação continuada ('Sim' quando a vigência for maior que 730 dias e tiver mais de uma parcela). + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. - name: contratos_comparativo_mensal description: > @@ -103,6 +106,9 @@ models: - name: siafi_valor_pago description: > Valor pago no mês de referência conforme registros do SIAFI. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. - name: contratos_somatorio description: > @@ -137,3 +143,6 @@ models: - name: total_pago description: > Soma de todos os valores pagos para o contrato segundo o SIAFI. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_empenhos.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_empenhos.sql index 6f9ef137..4eed67c3 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_empenhos.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_empenhos.sql @@ -48,7 +48,8 @@ with ne_ccor_ano_emissao, despesas_empenhadas, despesas_liquidadas, - despesas_pagas + despesas_pagas, + dt_ingest from full_join where origem = 'both' or origem = 'left only' -- contrato_id nulo significa lacuna no lado esquerdo do RIGHT JOIN, @@ -98,12 +99,12 @@ with ne_ccor_ano_emissao, despesas_empenhadas, despesas_liquidadas, - despesas_pagas + despesas_pagas, + dt_ingest from juncao_processo -- WHERE origem = 'both' where contrato_id is not null ), - -- --------------------------------------------------------------------------------------------------- empenhos_restantes_2 as ( select * @@ -142,11 +143,11 @@ with ne_ccor_ano_emissao, despesas_empenhadas, despesas_liquidadas, - despesas_pagas + despesas_pagas, + dt_ingest from juncao_cnpjs where contrato_id is not null ), - -- --------------------------------------------------------------------------------------------------- empenhos_restantes_3 as ( select @@ -184,7 +185,8 @@ with ne_ccor_ano_emissao, despesas_empenhadas, despesas_liquidadas, - despesas_pagas + despesas_pagas, + dt_ingest from juncao_info_complementar where contrato_id is not null ), diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_estagios.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_estagios.sql index 38141252..a833e1ee 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_estagios.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_estagios.sql @@ -21,7 +21,8 @@ with valor_liquidado, valor_pago, restos_a_pagar, - restos_a_pagar_pago + restos_a_pagar_pago, + dt_ingest from {{ ref("estagios_mensal") }} left join id_table_1 using (ne, cnpj_cpf) ), @@ -52,7 +53,8 @@ with valor_liquidado, valor_pago, restos_a_pagar, - restos_a_pagar_pago + restos_a_pagar_pago, + dt_ingest from empenhos_restantes_1 l left join id_table_2 r using (cnpj_cpf, num_processo) ), @@ -81,7 +83,8 @@ with valor_liquidado, valor_pago, restos_a_pagar, - restos_a_pagar_pago + restos_a_pagar_pago, + dt_ingest from empenhos_restantes_2 l left join id_table_3 r using (cnpj_cpf, info_complementar) ), @@ -105,7 +108,8 @@ select sum(valor_liquidado) as valor_liquidado, sum(valor_pago) as valor_pago, sum(restos_a_pagar) as restos_a_pagar, - sum(restos_a_pagar_pago) as restos_a_pagar_pago + sum(restos_a_pagar_pago) as restos_a_pagar_pago, + max(dt_ingest) as dt_ingest from result_table where contrato_id is not null group by 1, 2 diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_faturas.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_faturas.sql index ba4327f8..60dbc949 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_faturas.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_faturas.sql @@ -47,6 +47,7 @@ select f.id_empenho, f.numero_empenho, f.valor_empenho, - f.subelemento + f.subelemento, + f.dt_ingest from faturas_base f left join contratos c on f.contrato_id = c.contrato_id diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/cronogramas_faturas_mensal.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/cronogramas_faturas_mensal.sql index 444370f9..9cc2eb1e 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/cronogramas_faturas_mensal.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/cronogramas_faturas_mensal.sql @@ -17,7 +17,8 @@ with replace(replace(valorliquido::text, '.', ''), ',', '.')::numeric( 15, 2 ) as valorliquido, - situacao::text as situacao + situacao::text as situacao, + dt_ingest from {{ source("compras_gov", "faturas") }} ), @@ -30,7 +31,8 @@ with || split_part(emissao::text, '-', 2), 'YYYY-MM' ) as mes_ref, - sum(juros + multa + glosa + valorliquido) as valor_faturas_pagas + sum(juros + multa + glosa + valorliquido) as valor_faturas_pagas, + max(dt_ingest) as dt_ingest_pago from faturas_parsed where situacao = 'Pago' group by 1, 2 @@ -45,7 +47,8 @@ with || split_part(emissao::text, '-', 2), 'YYYY-MM' ) as mes_ref, - sum(juros + multa + glosa + valorliquido) as valor_faturas_pendentes + sum(juros + multa + glosa + valorliquido) as valor_faturas_pendentes, + max(dt_ingest) as dt_ingest_pendente from faturas_parsed where situacao = 'Pendente' group by 1, 2 @@ -67,7 +70,8 @@ with coalesce(valor_faturas_pendentes, 0) as valor_faturas_pendentes, coalesce(valor_cronograma, 0) - coalesce(valor_faturas_pagas, 0) - - coalesce(valor_faturas_pendentes, 0) as saldo_contratual_disponivel + - coalesce(valor_faturas_pendentes, 0) as saldo_contratual_disponivel, + greatest(dt_ingest_pago, dt_ingest_pendente) AS dt_ingest from joined_table order by contrato_id, mes_ref ) diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/estagios_mensal.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/estagios_mensal.sql index 67ae85f0..f990dcd9 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/estagios_mensal.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/estagios_mensal.sql @@ -17,7 +17,8 @@ with despesas_liquidadas as valor_liquidado, despesas_pagas as valor_pago, restos_a_pagar_inscritos as restos_a_pagar, - restos_a_pagar_pagos as restos_a_pagar_pago + restos_a_pagar_pagos as restos_a_pagar_pago, + dt_ingest from {{ ref("empenhos_tesouro") }} where true and ne_ccor != 'Total' ), @@ -35,7 +36,8 @@ with sum(valor_liquidado) as valor_liquidado, sum(valor_pago) as valor_pago, sum(restos_a_pagar) as restos_a_pagar, - sum(restos_a_pagar_pago) as restos_a_pagar_pago + sum(restos_a_pagar_pago) as restos_a_pagar_pago, + max(dt_ingest) as dt_ingest from parsed_estagios group by 1, 2, 3, 4, 5 order by 1, 2 @@ -59,7 +61,8 @@ with valor_liquidado, valor_pago, restos_a_pagar, - restos_a_pagar_pago + restos_a_pagar_pago, + dt_ingest from grouped_estagios ), @@ -77,7 +80,8 @@ with valor_liquidado, valor_pago, restos_a_pagar, - restos_a_pagar_pago + restos_a_pagar_pago, + dt_ingest from processo_fixed ), @@ -101,7 +105,8 @@ with then restos_a_pagar else - restos_a_pagar end as restos_a_pagar, - restos_a_pagar_pago + restos_a_pagar_pago, + dt_ingest from unnest_rap ), @@ -118,7 +123,8 @@ with valor_liquidado, valor_pago, restos_a_pagar, - restos_a_pagar_pago + restos_a_pagar_pago, + dt_ingest from fix_data ) diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/schema.yml b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/schema.yml index 60bed0fa..fb496acc 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/schema.yml @@ -55,6 +55,9 @@ models: - name: despesas_pagas description: > Valor total das despesas pagas para o contrato. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. - name: contratos_estagios description: > @@ -81,6 +84,9 @@ models: - name: valor_pago description: > Valor pago no mês de referência para o contrato. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. - name: estagios_mensal description: > @@ -115,6 +121,9 @@ models: - name: valor_pago description: > Valor total pago no mês e para o empenho específico. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. - name: contratos_faturas description: > @@ -227,6 +236,9 @@ models: - name: subelemento description: > Código do subelemento de despesa relacionado à fatura. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. - name: cronogramas_faturas_mensal description: > @@ -255,3 +267,6 @@ models: - name: saldo_contratual_disponivel description: > Diferença entre o valor programado no cronograma e a soma dos valores faturados (pagos e pendentes). + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. diff --git a/airflow_lappis/dags/dbt/ipea/models/orcamento_dbt/bronze/visao_orcamentaria_total.sql b/airflow_lappis/dags/dbt/ipea/models/orcamento_dbt/bronze/visao_orcamentaria_total.sql index 78edd3c7..cb60d080 100644 --- a/airflow_lappis/dags/dbt/ipea/models/orcamento_dbt/bronze/visao_orcamentaria_total.sql +++ b/airflow_lappis/dags/dbt/ipea/models/orcamento_dbt/bronze/visao_orcamentaria_total.sql @@ -38,7 +38,7 @@ with as restos_a_pagar_inscritos, {{ parse_financial_value("restos_a_pagar_pagos") }} as restos_a_pagar_pagos, - dt_ingest::timestamp as dt_ingest + (dt_ingest || '-03:00')::timestamptz as dt_ingest from source_data ) diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/afastamento_historico.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/afastamento_historico.sql index d2691627..0bf04e39 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/afastamento_historico.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/afastamento_historico.sql @@ -20,7 +20,8 @@ with datapublicacaoafastamento, descdiplomaafastamento, descocorrencia, - numerodiplomaafastamento + numerodiplomaafastamento, + dt_ingest -- grmatricula não está presente from {{ source("siape", "afastamento_historico") }} ), @@ -80,7 +81,8 @@ with ) as descocorrencia_clean, nullif( nullif(nullif(trim(numerodiplomaafastamento), ''), 'NaN'), '[null]' - ) as numerodiplomaafastamento_clean + ) as numerodiplomaafastamento_clean, + dt_ingest from afastamento_historico_raw ) @@ -126,5 +128,6 @@ select end as dt_publicacao_afastamento, descdiplomaafastamento_clean as desc_diploma_afastamento, descocorrencia_clean as desc_ocorrencia, - cast(numerodiplomaafastamento_clean as int) as numero_diploma_afastamento + cast(numerodiplomaafastamento_clean as int) as numero_diploma_afastamento, + (dt_ingest || '-03:00')::timestamptz as dt_ingest from afastamento_historico_cleaned diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/cargos_funcoes.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/cargos_funcoes.sql index 7505525c..21463d1b 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/cargos_funcoes.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/cargos_funcoes.sql @@ -18,7 +18,8 @@ with atonormativo__url, atonormativo__codigotipo, atonormativo__siglatipo, - denominacoes__denominacao + denominacoes__denominacao, + dt_ingest from {{ source("siorg", "cargos_funcao") }} ), @@ -55,5 +56,6 @@ select atonormativo__codigotipo, atonormativo__siglatipo, denominacao_codigo, - denominacao_descricao + denominacao_descricao, + (dt_ingest || '-03:00')::timestamptz as dt_ingest from denominacoes_expandidas diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_afastamento.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_afastamento.sql index d7cec9be..66bf946a 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_afastamento.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_afastamento.sql @@ -21,7 +21,8 @@ with descocorrencia, numerodiplomaafastamento, datainicioferiasinterrompidas, - diasrestantes + diasrestantes, + dt_ingest from {{ source("siape", "dados_afastamento") }} ), @@ -55,7 +56,8 @@ with nullif( trim(datainicioferiasinterrompidas), '' ) as datainicioferiasinterrompidas_clean, - nullif(trim(diasrestantes), '') as diasrestantes_clean + nullif(trim(diasrestantes), '') as diasrestantes_clean, + dt_ingest from dados_afastamento_raw ) @@ -102,5 +104,6 @@ select then to_date(datainicioferiasinterrompidas_clean, 'DDMMYYYY') else null end as dt_inicio_ferias_interrompidas, - cast(diasrestantes_clean as int) as dias_restantes + cast(diasrestantes_clean as int) as dias_restantes, + (dt_ingest || '-03:00')::timestamptz as dt_ingest from dados_afastamento_cleaned diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_curriculo.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_curriculo.sql index f73d779b..c613711f 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_curriculo.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_curriculo.sql @@ -16,7 +16,8 @@ with datafim, projeto, infoadicionais, - tipodesc + tipodesc, + dt_ingest from {{ source("siape", "dados_curriculo") }} ) @@ -39,5 +40,6 @@ select to_date(nullif(trim(datafim), '') || '01', 'YYYYMMDD') as dt_mes_fim, nullif(trim(projeto), '') as descricao_projeto, nullif(trim(infoadicionais), '') as informacoes_adicionais, - nullif(trim(tipodesc), '') as tipo_descricao + nullif(trim(tipodesc), '') as tipo_descricao, + (dt_ingest || '-03:00')::timestamptz as dt_ingest from dados_curriculo_raw diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_dependentes.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_dependentes.sql index a4f7954e..d34e7443 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_dependentes.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_dependentes.sql @@ -12,7 +12,8 @@ with codbeneficio, datafim, datainicio, - nomebeneficio + nomebeneficio, + dt_ingest from {{ source("siape", "dados_dependentes") }} ), @@ -29,7 +30,8 @@ with nullif(trim(codbeneficio), 'NaN') as codbeneficio, nullif(trim(datafim), 'NaN') as datafim, nullif(trim(datainicio), 'NaN') as datainicio, - nullif(trim(nomebeneficio), 'NaN') as nomebeneficio + nullif(trim(nomebeneficio), 'NaN') as nomebeneficio, + dt_ingest from dados_dependentes_raw ) @@ -46,5 +48,6 @@ select -- Converte para DATE, tratando '', 'NaN' e '00000000' como NULL to_date(nullif(nullif(datafim, ''), '00000000'), 'DDMMYYYY') as dt_fim, to_date(nullif(nullif(datainicio, ''), '00000000'), 'DDMMYYYY') as dt_inicio, - nullif(nomebeneficio, '') as nome_beneficio + nullif(nomebeneficio, '') as nome_beneficio, + (dt_ingest || '-03:00')::timestamptz as dt_ingest from dados_dependentes_cleaned diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_escolares.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_escolares.sql index c19aef12..eb282a35 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_escolares.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_escolares.sql @@ -9,7 +9,8 @@ with nometitulacao, codescolaridade, nomeescolaridade, - cpf + cpf, + dt_ingest from {{ source("siape", "dados_escolares") }} ) @@ -22,5 +23,6 @@ select nullif(trim(nometitulacao), '') as nome_titulacao, nullif(trim(codescolaridade), '') as cod_escolaridade, nullif(trim(nomeescolaridade), '') as nome_escolaridade, - regexp_replace(nullif(trim(cpf), ''), '[^0-9]', '', 'g') as cpf + regexp_replace(nullif(trim(cpf), ''), '[^0-9]', '', 'g') as cpf, + (dt_ingest || '-03:00')::timestamptz as dt_ingest from dados_escolares_raw diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_financeiros.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_financeiros.sql index 84f38deb..b1b6d980 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_financeiros.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_financeiros.sql @@ -11,7 +11,8 @@ with mesanopagamento, cpf, indicadormovsupl, - perubrica + perubrica, + dt_ingest from {{ source("siape", "dados_financeiros") }} ), @@ -27,7 +28,8 @@ with nullif(trim(mesanopagamento), '') as mes_ano_pagamento_str, nullif(trim(cpf), '') as cpf_str, nullif(trim(indicadormovsupl), '') as indicador_mov_supl, - nullif(trim(perubrica), '') as periodo_rubrica + nullif(trim(perubrica), '') as periodo_rubrica, + dt_ingest from dados_financeiros_raw ), @@ -91,7 +93,8 @@ with then '12' else null end as mes_num_pagamento, - substring(mes_ano_pagamento_str, 4, 4) as ano_pagamento + substring(mes_ano_pagamento_str, 4, 4) as ano_pagamento, + max(dt_ingest) over () dt_ingest_max from dados_financeiros_cleaned ) @@ -119,5 +122,6 @@ select ) as mes_ano_pagamento, regexp_replace(cpf_str, '[^0-9]', '', 'g') as cpf, indicador_mov_supl, - periodo_rubrica + periodo_rubrica, + (dt_ingest_max || '-03:00')::timestamptz as dt_ingest from conversao_mes diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_funcionais.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_funcionais.sql index 828c78ae..9608fd58 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_funcionais.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_funcionais.sql @@ -82,5 +82,6 @@ select replace(nullif(trim(valorvaletransporte), ''), ',', '.') as numeric ) as valor_vale_transporte, to_date(nullif(trim(datauorgexercicio), ''), 'DDMMYYYY') as dt_uorg_exercicio, - nullif(trim(pontuacaodesempenho), '') as pontuacao_desempenho -- Mantido como varchar (Não da pra saber se é "A","B" ou é um número > tudo null) + nullif(trim(pontuacaodesempenho), '') as pontuacao_desempenho, -- Mantido como varchar (Não da pra saber se é "A","B" ou é um número > tudo null) + (dt_ingest || '-03:00')::timestamptz as dt_ingest from dados_funcionais_raw diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_pa.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_pa.sql index c6fc3872..c0c9e907 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_pa.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_pa.sql @@ -12,7 +12,8 @@ with cpf_servidor, codvinculoservidor, nomealimentado, - nomevinculoservidor + nomevinculoservidor, + dt_ingest from {{ source("siape", "dados_pa") }} ) @@ -34,5 +35,6 @@ select regexp_replace(nullif(trim(cpf_servidor), ''), '[^0-9]', '', 'g') as cpf_servidor, nullif(trim(codvinculoservidor), '') as cod_vinculo_servidor, nullif(trim(nomealimentado), '') as nome_alimentado, - nullif(trim(nomevinculoservidor), '') as nome_vinculo_servidor + nullif(trim(nomevinculoservidor), '') as nome_vinculo_servidor, + (dt_ingest || '-03:00')::timestamptz as dt_ingest from dados_pa diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_pessoais.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_pessoais.sql index dfa338f0..3df21476 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_pessoais.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_pessoais.sql @@ -21,7 +21,8 @@ with coddeffisica, nomedeffisica, datachegbrasil, - nomepais + nomepais, + dt_ingest from {{ source("siape", "dados_pessoais") }} ) @@ -46,5 +47,6 @@ select nullif(trim(coddeffisica), '') as cod_deficiencia_fisica, nullif(trim(nomedeffisica), '') as nome_deficiencia_fisica, to_date(nullif(trim(datachegbrasil), ''), 'DDMMYYYY') as dt_chegada_brasil, - nullif(trim(nomepais), '') as nome_pais_origem + nullif(trim(nomepais), '') as nome_pais_origem, + (dt_ingest || '-03:00')::timestamptz as dt_ingest from dados_pessoais diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_uorg.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_uorg.sql index 29d86cfb..49020e90 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_uorg.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/dados_uorg.sql @@ -18,7 +18,8 @@ with ufuorg, cpf, complementouorg, - numfaxuorg + numfaxuorg, + dt_ingest from {{ source("siape", "dados_uorg") }} ) @@ -40,5 +41,6 @@ select upper(nullif(trim(ufuorg), '')) as uf_uorg, regexp_replace(nullif(trim(cpf), ''), '[^0-9]', '', 'g') as cpf, nullif(nullif(trim(complementouorg), ''), '---') as complemento_endereco_uorg, - regexp_replace(nullif(trim(numfaxuorg), ''), '[^0-9]', '', 'g') as fax_uorg + regexp_replace(nullif(trim(numfaxuorg), ''), '[^0-9]', '', 'g') as fax_uorg, + (dt_ingest || '-03:00')::timestamptz as dt_ingest from dados_uorg diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/estrutura_organizacional_cargos.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/estrutura_organizacional_cargos.sql index 28a95d10..d44b51dd 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/estrutura_organizacional_cargos.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/estrutura_organizacional_cargos.sql @@ -7,7 +7,8 @@ with municipio, uf, cargos, - ordem_grandeza + ordem_grandeza, + dt_ingest from {{ source("siorg", "estrutura_organizacional_cargos") }} ), @@ -19,6 +20,7 @@ with f.municipio, f.uf, f.ordem_grandeza, + f.dt_ingest, cargo_elem from fonte f, @@ -35,6 +37,7 @@ with ce.municipio, ce.uf, ce.ordem_grandeza, + ce.dt_ingest, cargo_elem ->> 'denominacao' as denominacao, cargo_elem ->> 'funcao' as funcao, instancia_elem ->> 'codigoInstancia' as codigo_instancia, @@ -70,5 +73,6 @@ select codigo_instancia, nome_titular, cpf_titular, - ordem_grandeza + ordem_grandeza, + (dt_ingest || '-03:00')::timestamptz as dt_ingest from instancias_filtradas diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_servidores.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_servidores.sql index b599073c..9ca75c25 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_servidores.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_servidores.sql @@ -1,6 +1,6 @@ with lista_servidores as ( - select cpf, dataultimatransacao as dt_ultima_transacao, coduorg as cod_uorg + select cpf, dataultimatransacao as dt_ultima_transacao, coduorg as cod_uorg, (dt_ingest || '-03:00')::timestamptz as dt_ingest from {{ source("siape", "lista_servidores") }} ) diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_uorgs.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_uorgs.sql index 1aea4a0a..94954e79 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_uorgs.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/lista_uorgs.sql @@ -1,6 +1,6 @@ with lista_uorgs as ( - select cast(codigo as int) as codigo, dt_ultima_transacao, nome + select cast(codigo as int) as codigo, dt_ultima_transacao, nome, (dt_ingest || '-03:00')::timestamptz as dt_ingest from {{ source("siape", "lista_uorgs") }} ) diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/schema.yml b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/schema.yml index 52834732..c3b66dfc 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/schema.yml @@ -54,6 +54,8 @@ models: description: "Descrição da ocorrência do afastamento." - name: numero_diploma_afastamento description: "Número do diploma legal do afastamento." + - name: dt_ingest + description: "Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw." - name: cargos_funcoes @@ -121,6 +123,9 @@ models: - name: denominacao_descricao description: "Descrição da denominação expandida para o cargo/função." + - name: dt_ingest + description: "Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw." + - name: dados_afastamento description: > @@ -167,6 +172,9 @@ models: description: "CPF do servidor." - name: dt_fim_aquisicao description: "Data final do período aquisitivo de férias." + - name: dt_ingest + description: "Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw." + - name: dados_curriculo @@ -228,6 +236,9 @@ models: - name: tipo_descricao description: "Tipo da descrição curricular. Pode indicar se o registro refere-se a curso, experiência, projeto etc." + - name: dt_ingest + description: "Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw." + - name: dados_dependentes description: > @@ -283,6 +294,9 @@ models: description: > Nome do benefício associado ao dependente ( auxílio-saúde, plano de assistência etc.). + - name: dt_ingest + description: "Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw." + - name: dados_escolares description: > @@ -320,6 +334,9 @@ models: - name: cpf description: "CPF do servidor, contendo apenas números, utilizado para identificação única." + - name: dt_ingest + description: "Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw." + - name: dados_financeiros @@ -373,6 +390,9 @@ models: description: > Período a que se refere a rubrica, podendo representar um agrupamento ou classificação contábil adicional." + - name: dt_ingest + description: "Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw." + - name: dados_funcionais description: > @@ -587,6 +607,9 @@ models: Pontuação atribuída ao servidor conforme avaliação de desempenho. Pode conter letras (A, B, C) ou valores numéricos, depende da origem. Mantido como string. + - name: dt_ingest + description: "Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw." + - name: dados_pa description: > @@ -619,6 +642,8 @@ models: description: "Nome do alimentado." - name: nome_vinculo_servidor description: "Nome do vínculo com o servidor." + - name: dt_ingest + description: "Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw." - name: dados_pessoais description: > @@ -669,6 +694,8 @@ models: description: "Data de chegada ao Brasil." - name: nome_pais_origem description: "Nome do país de origem." + - name: dt_ingest + description: "Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw." - name: dados_uorg description: > @@ -713,6 +740,8 @@ models: description: "Complemento do endereço da UORG." - name: fax_uorg description: "Fax da UORG." + - name: dt_ingest + description: "Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw." - name: estrutura_organizacional_cargos @@ -746,6 +775,8 @@ models: description: CPF do servidor titular do cargo (com apenas dígitos numéricos). - name: ordem_grandeza description: Nível hierárquico da unidade na estrutura, utilizado para escolher a instância mais relevante. + - name: dt_ingest + description: "Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw." - name: lista_servidores @@ -761,6 +792,8 @@ models: description: "Data da última transação." - name: cpf description: "CPF do servidor." + - name: dt_ingest + description: "Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw." - name: lista_uorgs description: > @@ -775,6 +808,8 @@ models: description: "Data da última transação." - name: nome description: "Nome da UORG." + - name: dt_ingest + description: "Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw." - name: terceirizados description: > @@ -816,6 +851,8 @@ models: description: Valor mensal do auxílio transporte recebido pelo trabalhador (convertido para numérico). - name: vale_alimentacao description: Valor mensal do vale alimentação recebido pelo trabalhador (convertido para numérico). + - name: dt_ingest + description: "Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw." - name: unidade_organizacional @@ -863,5 +900,7 @@ models: description: Nível hierárquico da unidade dentro da estrutura organizacional (quanto maior, mais profunda). - name: caminho_unidade description: Caminho hierárquico concatenado das siglas das unidades até o nível atual. + - name: dt_ingest + description: "Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw." diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/terceirizados.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/terceirizados.sql index fbfab585..fd5d5367 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/terceirizados.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/terceirizados.sql @@ -16,5 +16,6 @@ select replace(replace(aux_transporte, '.', ''), ',', '.')::numeric(15, 2) as aux_transporte, replace(replace(vale_alimentacao, '.', ''), ',', '.')::numeric( 15, 2 - ) as vale_alimentacao + ) as vale_alimentacao, + (dt_ingest || '-03:00')::timestamptz as dt_ingest from {{ source("compras_gov", "terceirizados") }} diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/unidade_organizacional.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/unidade_organizacional.sql index fd3c10dc..ef0d04f1 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/unidade_organizacional.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/bronze/unidade_organizacional.sql @@ -16,7 +16,8 @@ with recursive datafinalversaoconsulta, operacao, codigounidadepaianterior, - codigoorgaoentidadeanterior + codigoorgaoentidadeanterior, + (dt_ingest || '-03:00')::timestamptz as dt_ingest from {{ source("siorg", "unidade_organizacional") }} ), diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/aposentadorias_resumo.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/aposentadorias_resumo.sql index 4b306347..5e0cf404 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/aposentadorias_resumo.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/aposentadorias_resumo.sql @@ -13,7 +13,8 @@ with nome_ocorr_aposentadoria, nome_cargo, sigla_nivel_cargo, - cod_classe || '-' || cod_padrao as classe_padrao + cod_classe || '-' || cod_padrao as classe_padrao, + dt_ingest from {{ ref("servidores_detalhados") }} sd where dt_ocorr_aposentadoria is not null ) diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/cargos_consolidado.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/cargos_consolidado.sql index c215f4bb..96e000ee 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/cargos_consolidado.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/cargos_consolidado.sql @@ -22,7 +22,11 @@ select siape.nome_uorg_exercicio as siape_nome_uorg, siape.sigla_uorg_exercicio as siape_sigla_uorg, siape.uf_uorg as siape_uf_uorg, - siape.nome_situacao_funcional as siape_situacao_funcional + siape.nome_situacao_funcional as siape_situacao_funcional, + greatest( + siorg.dt_ingest, + siape.dt_ingest + ) as dt_ingest from {{ ref("servidores_detalhados") }} siape left join diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_genero.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_genero.sql index da8d756f..56b692f8 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_genero.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_genero.sql @@ -2,7 +2,8 @@ select nome_sexo as genero, count(*) as quantidade_servidores, - count(*) * 1.0 / sum(count(*)) over () as percentual_distribuicao + count(*) * 1.0 / sum(count(*)) over () as percentual_distribuicao, + max(dt_ingest) as dt_ingest from {{ ref("servidores_completos") }} group by nome_sexo order by percentual_distribuicao desc diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_mapa_uf.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_mapa_uf.sql index 71d75e21..3736e2e9 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_mapa_uf.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_mapa_uf.sql @@ -4,7 +4,8 @@ with -- Obter todos os servidores com localização servidores_localizacao as ( select distinct - df.cpf, du.uf_uorg, du.nome_municipio_uorg, df.nome_situacao_funcional + df.cpf, du.uf_uorg, du.nome_municipio_uorg, df.nome_situacao_funcional, + greatest(df.dt_ingest, du.dt_ingest) as dt_ingest from {{ ref("dados_funcionais") }} df inner join {{ ref("dados_uorg") }} du on df.sigla_uorg_exercicio = du.sigla_uorg where du.uf_uorg is not null @@ -12,7 +13,7 @@ with -- Contar servidores por UF contagem_por_uf as ( - select uf_uorg, count(distinct cpf) as valor + select uf_uorg, count(distinct cpf) as valor, max(dt_ingest) as dt_ingest_uf from servidores_localizacao group by uf_uorg ), @@ -29,7 +30,8 @@ select when coalesce(cpu.valor, 0) = 0 then '0%' else concat(round((coalesce(cpu.valor, 0) * 100.0 / ts.total), 0), '%') - end as percentual + end as percentual, + cpu.dt_ingest_uf as dt_ingest from {{ ref("estados_brasil") }} eb cross join total_servidores ts left join contagem_por_uf cpu on eb.sigla_uf = cpu.uf_uorg diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_raca_cor.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_raca_cor.sql index 67eb34d6..5c5a884b 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_raca_cor.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_raca_cor.sql @@ -1,5 +1,5 @@ -- Distribuição de servidores por raça/cor -select nome_cor as cor_raca, count(nome_cor) as quantidade_servidores +select nome_cor as cor_raca, count(nome_cor) as quantidade_servidores, max(dt_ingest) as dt_ingest from {{ ref("servidores_completos") }} group by nome_cor order by quantidade_servidores desc diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_situacao_funcional.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_situacao_funcional.sql index 80d24aec..4ff775b9 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_situacao_funcional.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_situacao_funcional.sql @@ -19,14 +19,16 @@ with then 'Ativo em outro órgão' else df.sigla_uorg_exercicio end as unidade_exercicio, - du.nome_municipio_uorg + du.nome_municipio_uorg, + greatest(df.dt_ingest, du.dt_ingest) as dt_ingest_max from {{ ref("dados_funcionais") }} df inner join {{ ref("dados_uorg") }} du on df.sigla_uorg_exercicio = du.sigla_uorg ) select nome_situacao_funcional as situacao_funcional_original, - count(nome_situacao_funcional) as quantidade_servidores + count(nome_situacao_funcional) as quantidade_servidores, + max(dt_ingest_max) as dt_ingest from dados_funcionais_enriquecidos group by nome_situacao_funcional order by quantidade_servidores desc diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/hierarquia.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/hierarquia.sql index bfaf74ee..2cf041de 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/hierarquia.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/hierarquia.sql @@ -23,7 +23,8 @@ with -- quanto menor a hierarquia, maior o cargo right(funcao_sigla, 2) as nivel_cargo, cast(substring(funcao_sigla, length(funcao_sigla) - 2, 1) as int) * 1000 - - cast(right(funcao, 2) as int) as hierarquia_cargo + - cast(right(funcao, 2) as int) as hierarquia_cargo, + greatest(eorg.dt_ingest, uo.dt_ingest) as dt_ingest_siorg from correcao_funcao as eorg inner join {{ ref("unidade_organizacional") }} as uo @@ -55,7 +56,8 @@ with uo.ordem_grandeza as ordem_grandeza_alternativa, substring(df.cod_funcao, 1, 1) || substring( df.cod_funcao, length(df.cod_funcao) - 2, 3 - ) as codigo_combinacao_siape + ) as codigo_combinacao_siape, + greatest(df.dt_ingest, uo.dt_ingest, uo.dt_ingest) as dt_ingest_siape from {{ ref("dados_funcionais") }} as df left join {{ ref("dados_pessoais") }} as dp on df.cpf = dp.cpf left join @@ -140,7 +142,8 @@ with coalesce(denominacao, nome_cargo) as nome_cargo, case when cod_situacao_funcional = '04' then 'Nomeação livre' else 'Carreira' - end as servidores_carreira + end as servidores_carreira, + greatest(pr.dt_ingest_siorg, pr.dt_ingest_siape, dp.dt_ingest) as dt_ingest from primeira_correlacao as pr left join {{ ref("dados_pessoais") }} as dp on pr.cpf_chefia_imediata = dp.cpf order by caminho_unidade, hierarquia_cargo diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/kpis_servidores.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/kpis_servidores.sql index 1d39e80d..f4da198c 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/kpis_servidores.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/kpis_servidores.sql @@ -1,47 +1,48 @@ with total_servidores as ( - select count(distinct cpf) as total from {{ ref("dados_funcionais") }} + select count(distinct cpf) as total, max(dt_ingest) as dt_ingest + from {{ ref("dados_funcionais") }} ), servidores_ativos as ( - select count(distinct cpf) as total + select count(distinct cpf) as total, max(dt_ingest) as dt_ingest from {{ ref("dados_funcionais") }} where nome_situacao_funcional in ('ATIVO PERMANENTE') ), aposentados as ( - select count(distinct cpf) as total + select count(distinct cpf) as total, max(dt_ingest) as dt_ingest from {{ ref("dados_funcionais") }} where nome_situacao_funcional in ('APOSENTADO') ), estagiarios as ( - select count(distinct cpf) as total + select count(distinct cpf) as total, max(dt_ingest) as dt_ingest from {{ ref("dados_funcionais") }} where nome_situacao_funcional in ('ESTAGIARIO SIGEPE') ), - terceirizados as (select count(distinct id) as total from {{ ref("terceirizados") }}) + terceirizados as (select count(distinct id) as total, max(dt_ingest) as dt_ingest from {{ ref("terceirizados") }}) -select 'total_servidores' as kpi, total as valor +select 'total_servidores' as kpi, total as valor, dt_ingest from total_servidores union all -select 'servidores_ativos_permanentes' as kpi, total as valor +select 'servidores_ativos_permanentes' as kpi, total as valor, dt_ingest from servidores_ativos union all -select 'aposentados' as kpi, total as valor +select 'aposentados' as kpi, total as valor, dt_ingest from aposentados union all -select 'estagiarios' as kpi, total as valor +select 'estagiarios' as kpi, total as valor, dt_ingest from estagiarios union all -select 'terceirizados' as kpi, total as valor +select 'terceirizados' as kpi, total as valor, dt_ingest from terceirizados diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/resumo_quadro_pessoal.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/resumo_quadro_pessoal.sql index 553259bb..368fb2b6 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/resumo_quadro_pessoal.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/resumo_quadro_pessoal.sql @@ -3,6 +3,7 @@ select coalesce(nome_sexo, 'N/A') as genero, coalesce(nome_situacao_funcional, 'N/A') as situacao_funcional, coalesce(uf_uorg, 'N/A') as localidade_uf, - count(distinct cpf) as quantidade_servidores + count(distinct cpf) as quantidade_servidores, + max(dt_ingest) as dt_ingest from {{ ref("servidores_detalhados") }} group by 1, 2, 3, 4 diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/schema.yml b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/schema.yml index d18433df..d4db042d 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/schema.yml @@ -42,6 +42,9 @@ models: description: Quantidade de meses entre o ingresso e a aposentadoria (ignora anos completos). - name: diff_dias description: Quantidade de dias entre o ingresso e a aposentadoria (ignora anos e meses completos). + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. @@ -97,6 +100,9 @@ models: description: Unidade federativa (UF) da unidade de exercício no SIAPE. - name: siape_situacao_funcional description: Situação funcional atual do servidor segundo o SIAPE. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. @@ -166,6 +172,9 @@ models: description: Nome ou denominação do cargo ocupado. - name: servidores_carreira description: Indica se o servidor está em cargo de carreira ou em nomeação livre. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. - name: resumo_quadro_pessoal @@ -196,6 +205,9 @@ models: - name: quantidade_servidores description: > Quantidade total de servidores distintos (com base no CPF) que se enquadram na combinação dos atributos anteriores. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. - name: distribuicao_genero @@ -217,6 +229,9 @@ models: description: > Percentual de servidores deste gênero em relação ao total de servidores, calculado como COUNT(*) * 1.0 / SUM(COUNT(*)) OVER (). + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. - name: distribuicao_raca_cor @@ -236,6 +251,9 @@ models: conforme autodeclaração registrada nos dados pessoais do SIAPE. - name: quantidade_servidores description: Quantidade total de servidores que se autodeclararam desta cor/raça. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. - name: distribuicao_situacao_funcional @@ -261,6 +279,9 @@ models: (ATIVO PERMANENTE, ATIVO EM OUTRO ORGAO, APOSENTADO, etc.). - name: quantidade_servidores description: Quantidade total de servidores que se enquadram nesta combinação de situação funcional. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. - name: kpis_servidores @@ -283,6 +304,9 @@ models: - name: valor description: > Valor numérico do KPI, representando a contagem de CPFs ou IDs únicos conforme a métrica. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. - name: distribuicao_mapa_uf @@ -305,6 +329,9 @@ models: description: Quantidade total de servidores com unidade de exercício neste estado. - name: percentual description: Percentual de servidores deste estado em relação ao total, formatado como string com símbolo '%'. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. - name: tabela_servidores_agregada @@ -331,4 +358,7 @@ models: description: Sigla da UF da unidade de exercício em maiúsculas. - name: total description: Quantidade total de servidores únicos (CPF) nesta combinação de atributos. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/tabela_servidores_agregada.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/tabela_servidores_agregada.sql index d9927289..2486bf81 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/tabela_servidores_agregada.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/tabela_servidores_agregada.sql @@ -9,7 +9,8 @@ with dp.nome_sexo as genero, df.nome_situacao_funcional as situacao, du.nome_municipio_uorg as cidade, - du.uf_uorg as estado + du.uf_uorg as estado, + greatest(df.dt_ingest, dp.dt_ingest, du.dt_ingest) as dt_ingest from {{ ref("dados_funcionais") }} df inner join {{ ref("dados_pessoais") }} dp on df.cpf = dp.cpf inner join {{ ref("dados_uorg") }} du on df.sigla_uorg_exercicio = du.sigla_uorg @@ -48,12 +49,13 @@ with end as situacao, initcap(cidade) as cidade, upper(estado) as estado, - count(distinct cpf) as total + count(distinct cpf) as total, + max(dt_ingest) as dt_ingest from servidores_completos group by nome_cargo, genero, situacao, cidade, estado ) -select cargo, genero, situacao, cidade, estado, total +select cargo, genero, situacao, cidade, estado, total, dt_ingest from servidores_agregados where total > 0 order by total desc, cargo, genero diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/afastamento_consolidado.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/afastamento_consolidado.sql index d04b306f..6feb49e1 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/afastamento_consolidado.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/afastamento_consolidado.sql @@ -23,7 +23,8 @@ with desc_ocorrencia, numero_diploma_afastamento, gr_matricula, - 'dados_afastamento' as origem_dados -- identificar a fonte + 'dados_afastamento' as origem_dados, -- identificar a fonte, + dt_ingest from {{ ref("dados_afastamento") }} union all @@ -50,7 +51,8 @@ with desc_ocorrencia, numero_diploma_afastamento, null as gr_matricula, -- não tem na afastamneto historico ... - 'afastamento_historico' as origem_dados -- identificar a fonte + 'afastamento_historico' as origem_dados, -- identificar a fonte, + dt_ingest from {{ ref("afastamento_historico") }} ), @@ -106,7 +108,8 @@ with desc_ocorrencia, numero_diploma_afastamento, gr_matricula, - origem_dados + origem_dados, + dt_ingest from prioridades where prioridade = 1 ) diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/quantitativo_alocados_ocupados.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/quantitativo_alocados_ocupados.sql index a76588fa..a1f3ba38 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/quantitativo_alocados_ocupados.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/quantitativo_alocados_ocupados.sql @@ -5,7 +5,7 @@ with ), codigos_siorg as ( - select funcao, nomeunidade, siglaunidade, denominacao, count(*) as qtd_vagas_cargo + select funcao, nomeunidade, siglaunidade, denominacao, count(*) as qtd_vagas_cargo, max(dt_ingest) as dt_ingest from siorg_sem_duplicatas group by funcao, nomeunidade, siglaunidade, denominacao ), @@ -16,7 +16,8 @@ with nome_uorg_exercicio, sigla_uorg_exercicio, nome_cargo, - count(*) as qtd_vagas_ocupadas + count(*) as qtd_vagas_ocupadas, + max(dt_ingest) as dt_ingest from siape_sem_duplicatas where cod_funcao is not null and dt_ocorr_aposentadoria is null group by cod_funcao, nome_uorg_exercicio, sigla_uorg_exercicio, nome_cargo @@ -33,7 +34,8 @@ with substring(replace(funcao, ' ', ''), 1, 1) || substring( replace(funcao, ' ', ''), length(replace(funcao, ' ', '')) - 2, 3 ) as codigo_combinacao_siorg, - qtd_vagas_cargo + qtd_vagas_cargo, + dt_ingest from codigos_siorg ), @@ -46,13 +48,26 @@ with substring(cod_funcao, 1, 1) || substring( cod_funcao, length(cod_funcao) - 2, 3 ) as codigo_combinacao_siape, - qtd_vagas_ocupadas + qtd_vagas_ocupadas, + dt_ingest from codigos_siape ), primeira_correlacao as ( select - *, + siorg.funcao, + siorg.nomeunidade, + siorg.siglaunidade, + siorg.denominacao, + siorg.codigo_combinacao_siorg, + siorg.qtd_vagas_cargo, + siape.cod_funcao, + siape.nome_uorg_exercicio, + siape.sigla_uorg_exercicio, + siape.nome_cargo, + siape.codigo_combinacao_siape, + siape.qtd_vagas_ocupadas, + greatest(siorg.dt_ingest, siape.dt_ingest) as dt_ingest, case when siorg.codigo_combinacao_siorg is not null @@ -88,5 +103,6 @@ select when qtd_vagas_ocupadas is null then qtd_vagas_cargo else (qtd_vagas_cargo - qtd_vagas_ocupadas) - end as qtd_cargos_vagos + end as qtd_cargos_vagos, + dt_ingest from primeira_correlacao diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/schema.yml b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/schema.yml index 350036a3..879c8e8f 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/schema.yml @@ -65,6 +65,9 @@ models: description: Código da função de chefia ou cargo de confiança ocupado pelo servidor no momento do afastamento. - name: sigla_uorg_exercicio description: Sigla da unidade organizacional onde o servidor exercia suas funções. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. @@ -97,6 +100,9 @@ models: description: > Diferença entre o número de vagas previstas (`qtd_vagas_cargo`) e as ocupadas (`qtd_vagas_ocupadas`). Indica o total de cargos vagos. Retorna `null` se a quantidade de vagas previstas não estiver disponível. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. - name: servidores_detalhados @@ -219,6 +225,9 @@ models: description: Valor mensal do vale transporte recebido. - name: pontuacao_desempenho description: Pontuação de desempenho do servidor, se aplicável. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. @@ -290,6 +299,9 @@ models: description: Denominação do cargo ocupado, conforme SIAPE ou SIORG. - name: servidores_carreira description: Classificação se o cargo é de carreira ou nomeação livre. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. - name: unidades_organizacionais_siorg_siape @@ -317,4 +329,7 @@ models: - "ambos": unidade presente no SIAPE e SIORG. - "apenas_siorg": presente apenas no SIORG. - "apenas_siape": presente apenas no SIAPE. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/servidores_completos.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/servidores_completos.sql index 1ae2bc94..b92ada3d 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/servidores_completos.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/servidores_completos.sql @@ -4,7 +4,35 @@ with hierarquia_enriquecida as ( select - ph.*, + ph.codigo_siape, + ph.codigo_siorg, + ph.codigo_combinacao_siape, + ph.codigo_combinacao_siorg, + ph.matricula_siape, + ph.cpf, + ph.cpf_chefia_imediata, + ph.cod_situacao_funcional, + ph.nome_situacao_funcional, + ph.hierarquia_cargo, + ph.servidor, + ph.dt_nascimento, + ph.nome_sexo, + ph.nome_estado_civil, + ph.nome_nacionalidade, + ph.nome_cor, + ph.uf_nascimento, + ph.nome_municipio_nascimento, + ph.nome_chefia, + ph.codigounidade, + ph.codigounidadepai, + ph.caminho_unidade, + ph.ordem_grandeza, + ph.nomeunidade, + ph.siglaunidade, + ph.nome_cargo, + ph.servidores_carreira, + ph.dt_ingest as dt_ingest_ph, + df.dt_ingest as dt_ingest_df, case when df.modalidade_pgd is null then 'Não participa' @@ -27,17 +55,47 @@ with ), servidores_enriquecidos as ( - select distinct ph.*, du.nome_municipio_uorg + select distinct ph.*, du.nome_municipio_uorg, du.dt_ingest as dt_ingest_du from hierarquia_enriquecida ph inner join {{ ref("dados_uorg") }} du on ph.siglaunidade = du.sigla_uorg order by caminho_unidade, hierarquia_cargo ) select distinct - se.*, + se.codigo_siape, + se.codigo_siorg, + se.codigo_combinacao_siape, + se.codigo_combinacao_siorg, + se.matricula_siape, + se.cpf, + se.cpf_chefia_imediata, + se.cod_situacao_funcional, + se.nome_situacao_funcional, + se.hierarquia_cargo, + se.servidor, + se.dt_nascimento, + se.nome_sexo, + se.nome_estado_civil, + se.nome_nacionalidade, + se.nome_cor, + se.uf_nascimento, + se.nome_municipio_nascimento, + se.nome_chefia, + se.codigounidade, + se.codigounidadepai, + se.caminho_unidade, + se.ordem_grandeza, + se.nomeunidade, + se.siglaunidade, + se.nome_cargo, + se.servidores_carreira, + se.pdg, + se.unidade_exercicio, + se.nome_municipio_uorg, sd.cod_escolaridade_principal, sd.nome_escolaridade_principal, sd.nome_deficiencia_fisica, - sd.nome_cargo as nome_cargo_emprego + sd.nome_cargo as nome_cargo_emprego, + greatest(se.dt_ingest_ph, se.dt_ingest_df, se.dt_ingest_du, sd.dt_ingest) as dt_ingest from servidores_enriquecidos se inner join {{ ref("servidores_detalhados") }} sd on se.cpf = sd.cpf diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/servidores_detalhados.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/servidores_detalhados.sql index 2c6b5f23..03a04a6e 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/servidores_detalhados.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/servidores_detalhados.sql @@ -1,6 +1,6 @@ with educacao_principal as ( - select cpf, cod_escolaridade, nome_escolaridade, cod_titulacao, nome_titulacao + select cpf, cod_escolaridade, nome_escolaridade, cod_titulacao, nome_titulacao, dt_ingest as dt_ingest_ep from {{ ref("dados_escolares") }} ), uorg_completo as ( @@ -22,7 +22,8 @@ with du.uf_uorg, du.cpf, du.complemento_endereco_uorg, - du.fax_uorg + du.fax_uorg, + du.dt_ingest as dt_ingest_du -- lu.dt_ultima_transacao AS dt_ultima_transacao_uorg, os codigos não batem e a -- informação aparentemente ja existe... -- lu.nome AS nome_uorg_lista @@ -142,7 +143,14 @@ select uorg_c.sigla_uorg, uorg_c.uf_uorg, uorg_c.complemento_endereco_uorg, - uorg_c.fax_uorg + uorg_c.fax_uorg, + + greatest( + dp.dt_ingest, + df.dt_ingest, + ep.dt_ingest_ep, + uorg_c.dt_ingest_du + ) as dt_ingest from {{ ref("dados_pessoais") }} dp left join {{ ref("dados_funcionais") }} df on dp.cpf = df.cpf diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/tabela_correlacao_cargos.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/tabela_correlacao_cargos.sql index 907cbd0b..aa5e5bf3 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/tabela_correlacao_cargos.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/tabela_correlacao_cargos.sql @@ -1,7 +1,10 @@ with correcao_funcao as ( - select *, replace(funcao, ' ', '') as funcao_sigla + select + *, + replace(funcao, ' ', '') as funcao_sigla, + dt_ingest as dt_ingest_estrutura from {{ ref("estrutura_organizacional_cargos") }} ), @@ -13,6 +16,7 @@ with eorg.ordem_grandeza, eorg.denominacao, eorg.codigo_instancia, + eorg.dt_ingest_estrutura, uo.codigounidadepai, uo.caminho_unidade, case @@ -24,7 +28,8 @@ with -- quanto menor a hierarquia, maior o cargo right(funcao_sigla, 2) as nivel_cargo, cast(substring(funcao_sigla, length(funcao_sigla) - 2, 1) as int) * 1000 - - cast(right(funcao, 2) as int) as hierarquia_cargo + - cast(right(funcao, 2) as int) as hierarquia_cargo, + uo.dt_ingest as dt_ingest_uorg from correcao_funcao as eorg inner join {{ ref("unidade_organizacional") }} as uo @@ -56,7 +61,10 @@ with uo.ordem_grandeza as ordem_grandeza_alternativa, substring(df.cod_funcao, 1, 1) || substring( df.cod_funcao, length(df.cod_funcao) - 2, 3 - ) as codigo_combinacao_siape + ) as codigo_combinacao_siape, + df.dt_ingest as dt_ingest_funcionais, + dp.dt_ingest as dt_ingest_pessoais, + uo.dt_ingest as dt_ingest_uorg_alt from {{ ref("dados_funcionais") }} as df left join {{ ref("dados_pessoais") }} as dp on df.cpf = dp.cpf left join @@ -161,7 +169,15 @@ with coalesce(denominacao, nome_cargo) as nome_cargo, case when cod_situacao_funcional = '04' then 'Nomeação livre' else 'Carreira' - end as servidores_carreira + end as servidores_carreira, + + greatest( + pr.dt_ingest_estrutura, + pr.dt_ingest_uorg, + pr.dt_ingest_funcionais, + pr.dt_ingest_pessoais, + pr.dt_ingest_uorg_alt + ) as dt_ingest from primeira_correlacao as pr left join {{ ref("dados_pessoais") }} as dp on pr.cpf_chefia_imediata = dp.cpf order by caminho_unidade, hierarquia_cargo diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/unidades_organizacionais_siorg_siape.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/unidades_organizacionais_siorg_siape.sql index 263e7893..b5af5981 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/unidades_organizacionais_siorg_siape.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/unidades_organizacionais_siorg_siape.sql @@ -1,11 +1,13 @@ with preparacao as ( - select distinct + select du.codigo_orgao::integer as codigo_orgao, du.codigo_orgao_uorg as combinacao_codigo_lista, (right(du.codigo_orgao_uorg, 7))::integer as codigo_lista_uorg, - du.sigla_uorg as sigla_uorg + du.sigla_uorg as sigla_uorg, + max(du.dt_ingest) as dt_ingest_du from {{ ref("dados_uorg") }} du + group by 1, 2, 3, 4 ), join_lista_uorgo_dados_uorg as ( @@ -14,10 +16,13 @@ with p.combinacao_codigo_lista, p.codigo_lista_uorg, p.sigla_uorg, + p.dt_ingest_du, lu.dt_ultima_transacao, - lu.nome as nome_unidade + lu.nome as nome_unidade, + max(lu.dt_ingest) as dt_ingest_lu from preparacao p join {{ ref("lista_uorgs") }} lu on p.codigo_lista_uorg = lu.codigo + group by 1, 2, 3, 4, 5, 6, 7 ), unidade_organizacional as ( @@ -32,6 +37,7 @@ with coalesce(a.sigla_uorg, sigla_unidade) as sigla_uorg, a.codigo_lista_uorg as codigo_unidade_siape, uo.codigounidade as codigo_unidade_siorg, + greatest(a.dt_ingest_du, a.dt_ingest_lu) as dt_ingest, case when a.nome_unidade is null and uo.nome is not null then 'apenas_siorg' diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/nc_tesouro.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/nc_tesouro.sql index add24d65..a12edbbf 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/nc_tesouro.sql +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/nc_tesouro.sql @@ -21,7 +21,8 @@ with favorecido_doc, favorecido_doc_descricao, replace(nc_valor_linha, ',', '.')::numeric(15, 2) as nc_valor_linha, - {{ parse_financial_value("movimento_liquido") }} as movimento_liquido + {{ parse_financial_value("movimento_liquido") }} as movimento_liquido, + (dt_ingest || '-03:00')::timestamptz as dt_ingest from {{ source("siafi", "nc_tesouro") }} ) diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/pf_tesouro.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/pf_tesouro.sql index 26b3b9a3..c9b0d593 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/pf_tesouro.sql +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/pf_tesouro.sql @@ -22,7 +22,8 @@ with pf_recurso, pf_recurso_descricao, doc_observacao, - replace(pf_valor_linha, ',', '.')::numeric(15, 2) as pf_valor_linha + replace(pf_valor_linha, ',', '.')::numeric(15, 2) as pf_valor_linha, + (dt_ingest || '-03:00')::timestamptz as dt_ingest from {{ source("siafi", "pf_tesouro") }} ) diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/planos_acao.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/planos_acao.sql index 1a4c3147..5ffb801a 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/planos_acao.sql +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/planos_acao.sql @@ -26,7 +26,8 @@ with vl_beneficiario_especifico::numeric(15, 2) as vl_beneficiario_especifico, vl_chamamento_publico::numeric(15, 2) as vl_chamamento_publico, sq_instrumento, - aa_instrumento + aa_instrumento, + (dt_ingest || '-03:00')::timestamptz as dt_ingest from {{ source("transfere_gov", "planos_acao") }} ) diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/schema.yml b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/schema.yml index c81f12a2..2e2bfc38 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/bronze/schema.yml @@ -74,6 +74,9 @@ models: - name: pf_valor_linha description: > Valor monetário individual da linha da Programação Financeira. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. data_tests: - verificacao_tipagem: nome_tabela: 'ted.pf_tesouro' @@ -152,6 +155,9 @@ models: - name: movimento_liquido description: > Valor líquido do movimento financeiro associado à Nota de Crédito, após deduções ou acréscimos aplicáveis. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. - name: planos_acao description: > @@ -223,3 +229,6 @@ models: - name: aa_instrumento description: > Ano de referência do instrumento associado ao Plano de Ação. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/schema.yml b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/schema.yml index e054b408..415d8375 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/schema.yml @@ -58,4 +58,7 @@ models: - name: financeiro_cancelado description: > Valor total de cancelamentos financeiros identificados na programação, com ação 'CANCELAMENTO'. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/ted_resumo_orcamentario.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/ted_resumo_orcamentario.sql index 9b66c50b..afab4b90 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/ted_resumo_orcamentario.sql +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/ted_resumo_orcamentario.sql @@ -8,7 +8,8 @@ with id_plano_acao as plano_acao, vl_total_plano_acao as valor_firmado, sigla_unidade_descentralizada, - ted_beneficiario_emitente + ted_beneficiario_emitente, + dt_ingest as dt_ingest_vf from {{ ref("planos_acao") }} ), @@ -23,7 +24,8 @@ with ) as orcamento_recebido, sum( case when nc_evento in ('300301', '300307') then nc_valor else 0 end - ) as orcamento_devolvido + ) as orcamento_devolvido, + max(dt_ingest) as dt_ingest_vo from {{ ref("nc_plano_acao") }} where ptres not in ('-9') group by plano_acao, num_transf @@ -46,7 +48,8 @@ with sum(despesas_pagas) as despesas_pagas_exercicio, sum(restos_a_pagar_pagos) as despesas_pagas_rap, sum(restos_a_pagar_inscritos) as restos_a_pagar, - sum(despesas_liquidadas) as despesas_liquidada + sum(despesas_liquidadas) as despesas_liquidada, + max(dt_ingest) as dt_ingest_ve from {{ ref("empenhos_plano_acao") }} group by plano_acao, num_transf ), @@ -67,17 +70,20 @@ with ) as financeiro_devolvido, sum( case when pf_acao = 'CANCELAMENTO' then pf_valor_linha else 0 end - ) as financeiro_cancelado + ) as financeiro_cancelado, + max(dt_ingest) as dt_ingest_vfin from {{ ref("pf_plano_acao") }} group by plano_acao, num_transf ), -- Saldo financeiro = Financeiro recebido - Financeiro devolvido - Utilizado/pago -- Financeiro a receber = Valor firmado - Financeiro recebido + Financeiro devolvido join_parcial as ( - select * - from valores_orcamentos_tb - full join valores_empenhados_tb using (plano_acao, num_transf) - full join valores_financeiro_tb using (plano_acao, num_transf) + select + *, + greatest(vo.dt_ingest_vo, ve.dt_ingest_ve, vfin.dt_ingest_vfin) as dt_ingest_jp + from valores_orcamentos_tb vo + full join valores_empenhados_tb ve using (plano_acao, num_transf) + full join valores_financeiro_tb vfin using (plano_acao, num_transf) ) -- Final @@ -103,7 +109,8 @@ select despesas_liquidada, financeiro_recebido, financeiro_devolvido, - financeiro_cancelado -from valor_firmado_tb -full join join_parcial using (plano_acao) + financeiro_cancelado, + greatest(vf.dt_ingest_vf, jp.dt_ingest_jp) as dt_ingest +from valor_firmado_tb vf +full join join_parcial jp using (plano_acao) where (plano_acao is not null) or (num_transf is not null) diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/nc_plano_acao.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/nc_plano_acao.sql index bb72e276..bcf2e990 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/nc_plano_acao.sql +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/nc_plano_acao.sql @@ -25,7 +25,8 @@ with when nc_evento in ('300302', '300308', '300311', '300083') then (-1) * nc_valor_linha else nc_valor_linha - end as nc_valor + end as nc_valor, + rd.dt_ingest from raw_data rd left join planos_de_acao pda on rd.nc_transferencia = pda.num_transf ) diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/pf_plano_acao.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/pf_plano_acao.sql index c60f078e..dd877ade 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/pf_plano_acao.sql +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/pf_plano_acao.sql @@ -11,7 +11,8 @@ with pf_evento, pf_evento_descricao, substring(pf_acao_descricao, '(\w+) ') as pf_acao, - pf_valor_linha + pf_valor_linha, + dt_ingest as dt_ingest_pf from {{ ref("pf_tesouro") }} ), @@ -19,24 +20,55 @@ with select tx_numero_programacao as pf, ug_emitente_programacao as ug_emitente, - id_plano_acao as plano_acao - from {{ source("transfere_gov", "programacao_financeira") }} + id_plano_acao as plano_acao, + (dt_ingest || '-03:00')::timestamptz as dt_ingest_tg + from {{ source("transfere_gov", "programacao_financeira") }} -- raw ), joined_by_transfere_gov as ( - select pf.*, t.plano_acao + select + pf, + num_transf, + emissao_mes, + emissao_dia, + ug_emitente, + ug_favorecido, + pf_evento, + pf_evento_descricao, + pf_acao, + pf_valor_linha, + t.plano_acao, + greatest(pf.dt_ingest_pf, t.dt_ingest_tg) as dt_ingest from programacoes_financeira pf inner join pf_transfere_gov t using (pf, ug_emitente) ), joined_by_num_transf as ( - select pf.*, v.plano_acao + select + pf.pf, + pf.num_transf, + pf.emissao_mes, + pf.emissao_dia, + pf.ug_emitente, + pf.ug_favorecido, + pf.pf_evento, + pf.pf_evento_descricao, + pf.pf_acao, + pf.pf_valor_linha, + v.plano_acao, + pf.dt_ingest_pf as dt_ingest from programacoes_financeira pf inner join {{ ref("num_transf_n_plano_acao") }} v using (num_transf) + -- Exclui registros que já existem em joined_by_transfere_gov + where not exists ( + select 1 + from pf_transfere_gov t + where t.pf = pf.pf and t.ug_emitente = pf.ug_emitente + ) ) select * from joined_by_transfere_gov -union +union all select * -from joined_by_num_transf +from joined_by_num_transf \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/schema.yml b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/schema.yml index 451ea4c8..55b0523b 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/silver/schema.yml @@ -31,6 +31,9 @@ models: - name: demais_colunas description: > Outras colunas provenientes da tabela `empenhos_tesouro`, contendo informações adicionais sobre os empenhos, como data de emissão, valor, unidade gestora, entre outras. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. - name: nc_plano_acao description: > @@ -73,6 +76,9 @@ models: - name: nc_valor description: > Valor monetário da Nota de Crédito, ajustado conforme o tipo de evento contábil. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. - name: pf_plano_acao description: > @@ -114,3 +120,6 @@ models: - name: plano_acao description: > Identificador do Plano de Ação associado à Programação Financeira, determinado por correspondência direta com o sistema TransfereGov ou via número de transferência. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. From d5ff59433e997e20b2e8d9e085457639889f90ad Mon Sep 17 00:00:00 2001 From: Tiago Bittencourt Date: Mon, 2 Mar 2026 10:27:12 -0300 Subject: [PATCH 201/317] =?UTF-8?q?feat:=20dag=20extra=C3=A7=C3=A3o=20fina?= =?UTF-8?q?lidade=20paginada=20(#68)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../finalidade_especial_ingest_dag.py | 71 ++++++++++++++ .../plugins/cliente_transferegov_emendas.py | 96 +++++++++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/transferegov_emendas/finalidade_especial_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/finalidade_especial_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/finalidade_especial_ingest_dag.py new file mode 100644 index 00000000..d55cb174 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/finalidade_especial_ingest_dag.py @@ -0,0 +1,71 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule +from postgres_helpers import get_postgres_conn +from cliente_transferegov_emendas import ClienteTransfereGov +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval=get_dynamic_schedule("finalidade_especial_ingest_dag"), + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Tiago", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["transfere_gov_api", "finalidade_especial"], +) +def api_finalidade_especial_dag() -> None: + """DAG para buscar e armazenar finalidades especiais do Transfere Gov.""" + + @task + def fetch_and_store_finalidade_especial() -> None: + logging.info( + "[finalidade_especial_ingest_dag.py] Iniciando extração finalidades especiais" + ) + + api = ClienteTransfereGov() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + # Busca todas as finalidades especiais com paginação automática + finalidades_data = api.get_all_finalidades_especiais(page_size=1000) + + if finalidades_data and len(finalidades_data) > 0: + # Adicionar dt_ingest a cada documento + for documento in finalidades_data: + documento["dt_ingest"] = datetime.now().isoformat() + + # Inserir/atualizar dados no banco + logging.info( + f"[finalidade_especial_ingest_dag.py] Inserindo " + f"{len(finalidades_data)} finalidades especiais no " + f"schema transfere_gov" + ) + db.insert_data( + finalidades_data, + "finalidades_especiais", + conflict_fields=["id_executor", "cd_area_politica_publica_tipo_pt", + "area_politica_publica_pt"], + primary_key=["id_executor", "cd_area_politica_publica_tipo_pt", + "area_politica_publica_pt"], + schema="transferegov_emendas", + ) + + logging.info( + f"[finalidade_especial_ingest_dag.py] Concluído. " + f"Total de {len(finalidades_data)} finalidades especiais " + f"inseridas/atualizadas" + ) + else: + logging.warning( + "[finalidade_especial_ingest_dag.py] Nenhuma finalidade especial " + "encontrada" + ) + + fetch_and_store_finalidade_especial() + +api_finalidade_especial_dag() diff --git a/airflow_lappis/plugins/cliente_transferegov_emendas.py b/airflow_lappis/plugins/cliente_transferegov_emendas.py index 1e53ccb4..467be51a 100644 --- a/airflow_lappis/plugins/cliente_transferegov_emendas.py +++ b/airflow_lappis/plugins/cliente_transferegov_emendas.py @@ -762,3 +762,99 @@ def get_all_metas_especiais(self, page_size: int = 1000) -> list: f"Total metas especiais: {len(all_data)}" ) return all_data + + def get_finalidades_especiais( + self, limit: int = 1000, offset: int = 0 + ) -> Optional[list]: + """ + Obter finalidades especiais com paginação. + + Args: + limit (int): Quantidade de registros por página (padrão: 1000) + offset (int): Deslocamento inicial (padrão: 0) + + Returns: + list: lista de finalidades especiais ou None se falhar + """ + endpoint = "finalidade_especial" + params = { + "select": "*", + "order": "id_executor.asc", + "limit": limit, + "offset": offset, + } + + logging.info( + f"[cliente_transfere_gov.py] Fetching finalidades especiais with " + f"limit={limit}, offset={offset}" + ) + + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER, params=params + ) + + if status == http.HTTPStatus.OK and isinstance(data, list): + logging.info( + f"[cliente_transfere_gov.py] Successfully fetched {len(data)} " + "finalidades especiais" + ) + return data + else: + logging.warning( + f"[cliente_transfere_gov.py] Failed to fetch finalidades especiais " + f"with status: {status}" + ) + return None + + def get_all_finalidades_especiais(self, page_size: int = 1000) -> list: + """ + Obter todas as finalidades especiais com paginação automática. + + Args: + page_size (int): Quantidade de registros por requisição (padrão: 1000) + + Returns: + list: lista completa de finalidades especiais + """ + all_data = [] + offset = 0 + page = 1 + + logging.info( + "[cliente_transfere_gov.py] Starting full extraction of " + "finalidades especiais" + ) + + while True: + logging.info( + f"[cliente_transfere_gov.py] Fetching page {page} " f"(offset: {offset})" + ) + + data = self.get_finalidades_especiais(limit=page_size, offset=offset) + + if not data or len(data) == 0: + logging.info( + "[cliente_transfere_gov.py] No more data received. " + "Extraction complete." + ) + break + + all_data.extend(data) + logging.info( + f"[cliente_transfere_gov.py] Page {page} fetched: {len(data)} records. " + f"Total so far: {len(all_data)}" + ) + + # Se recebemos menos registros que o limite, é a última página + if len(data) < page_size: + logging.info("[cliente_transfere_gov.py] Last page reached.") + break + + offset += page_size + page += 1 + + logging.info( + f"[cliente_transfere_gov.py] Extraction completed. " + f"Total records: {len(all_data)}" + ) + return all_data From da376a4f628d0998fff861ff030368a87b7ffd5a Mon Sep 17 00:00:00 2001 From: Mateushqms Date: Wed, 4 Mar 2026 10:17:56 -0300 Subject: [PATCH 202/317] feat: criacao da dag e cliente para extrair os partidos dos deputados --- .../dados_abertos/deputados_ingest_dag.py | 61 +++++++++++++++++ airflow_lappis/plugins/cliente_deputados.py | 66 +++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/dados_abertos/deputados_ingest_dag.py create mode 100644 airflow_lappis/plugins/cliente_deputados.py diff --git a/airflow_lappis/dags/data_ingest/dados_abertos/deputados_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_abertos/deputados_ingest_dag.py new file mode 100644 index 00000000..ca13addc --- /dev/null +++ b/airflow_lappis/dags/data_ingest/dados_abertos/deputados_ingest_dag.py @@ -0,0 +1,61 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule +from postgres_helpers import get_postgres_conn +from cliente_deputados import ClienteDeputados +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval=get_dynamic_schedule("deputados_ingest_dag"), + start_date=datetime(2025, 1, 1), + catchup=False, + default_args={ + "owner": "Leonardo e Mateus", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["camara_deputados", "deputados", "dados_abertos"], +) +def deputados_ingest_dag() -> None: + """DAG para buscar e armazenar dados de deputados da Câmara dos Deputados.""" + + @task + def fetch_and_store_deputados() -> None: + logging.info("[deputados_ingest_dag.py] Iniciando extração de deputados") + + api = ClienteDeputados() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + deputados_data = api.get_all_deputados() + + if deputados_data and len(deputados_data) > 0: + for item in deputados_data: + item["dt_ingest"] = datetime.now().isoformat() + + logging.info( + f"[deputados_ingest_dag.py] Inserindo " + f"{len(deputados_data)} deputados no schema camara_deputados" + ) + + db.insert_data( + deputados_data, + "deputados", + conflict_fields=["id"], + primary_key=["id"], + schema="camara_deputados", + ) + + logging.info( + f"[deputados_ingest_dag.py] Concluído. " + f"Total de {len(deputados_data)} registros processados." + ) + else: + logging.warning("[deputados_ingest_dag.py] Nenhum deputado encontrado") + + fetch_and_store_deputados() + + +deputados_ingest_dag() diff --git a/airflow_lappis/plugins/cliente_deputados.py b/airflow_lappis/plugins/cliente_deputados.py new file mode 100644 index 00000000..0411103c --- /dev/null +++ b/airflow_lappis/plugins/cliente_deputados.py @@ -0,0 +1,66 @@ +import http +import logging +from typing import Any +from cliente_base import ClienteBase + + +class ClienteDeputados(ClienteBase): + """ + Cliente para consumir a API de Dados Abertos da Câmara dos Deputados. + """ + + BASE_URL = "https://dadosabertos.camara.leg.br/api/v2" + BASE_HEADER = {"accept": "application/json"} + + def __init__(self) -> None: + super().__init__(base_url=ClienteDeputados.BASE_URL) + logging.info( + "[cliente_deputados.py] Initialized ClienteDeputados with base_url: " + f"{ClienteDeputados.BASE_URL}" + ) + + def get_deputados(self, **params: Any) -> list: + """ + Obter lista de deputados + """ + endpoint = "/deputados" + logging.info(f"[cliente_deputados.py] Fetching deputados with params: {params}") + + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER, params=params + ) + + if status == http.HTTPStatus.OK and isinstance(data, dict): + deputados: list[dict[str, Any]] = data.get("dados", []) + logging.info( + f"[cliente_deputados.py] Successfully fetched {len(deputados)} deputados" + ) + return deputados + else: + logging.warning( + f"[cliente_deputados.py] Failed to fetch deputados with status: {status}" + ) + return None + + def get_all_deputados(self) -> list: + """ + Itera por todas as páginas da API e retorna a lista completa de deputados. + """ + all_deputados = [] + pagina = 1 + + while True: + params = {"pagina": pagina, "itens": 100} + deputados = self.get_deputados(**params) + + if not deputados: + break + + all_deputados.extend(deputados) + + if len(deputados) < 100: + break + + pagina += 1 + + return all_deputados From ae81b84586b6c2c384e7d59a9f23e62ff6046e04 Mon Sep 17 00:00:00 2001 From: Mateushqms Date: Wed, 4 Mar 2026 12:04:21 -0300 Subject: [PATCH 203/317] feat: criacao da camanda bronze para deputados --- airflow_lappis/dags/dbt/ipea/dbt_project.yml | 4 + .../models/deputados_dbt/bronze/deputados.sql | 18 ++++ .../models/deputados_dbt/bronze/schema.yaml | 83 +++++++++++++++++++ .../dags/dbt/ipea/models/sources.yml | 5 ++ 4 files changed, 110 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/deputados_dbt/bronze/deputados.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/deputados_dbt/bronze/schema.yaml diff --git a/airflow_lappis/dags/dbt/ipea/dbt_project.yml b/airflow_lappis/dags/dbt/ipea/dbt_project.yml index 7bf6e704..430d96a1 100755 --- a/airflow_lappis/dags/dbt/ipea/dbt_project.yml +++ b/airflow_lappis/dags/dbt/ipea/dbt_project.yml @@ -50,6 +50,10 @@ models: # +schema: siape # views: # +materialized: view + deputados_dbt: + +materialized: table + +schema: deputados + on-run-start: - '{{create_udfs()}}' \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/ipea/models/deputados_dbt/bronze/deputados.sql b/airflow_lappis/dags/dbt/ipea/models/deputados_dbt/bronze/deputados.sql new file mode 100644 index 00000000..ea893a30 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/deputados_dbt/bronze/deputados.sql @@ -0,0 +1,18 @@ +{{ config(materialized="table") }} + +with + deputados_raw as ( + select + -- Conversão de tipos e formatação de colunas + id::integer as id, + nome::text as nome, + siglapartido::text as siglapartido, + siglauf::text as siglauf, + idlegislatura::integer as idlegislatura, + email::text as email, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("dados_abertos", "deputados") }} + ) + +select * +from deputados_raw diff --git a/airflow_lappis/dags/dbt/ipea/models/deputados_dbt/bronze/schema.yaml b/airflow_lappis/dags/dbt/ipea/models/deputados_dbt/bronze/schema.yaml new file mode 100644 index 00000000..9733b1c7 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/deputados_dbt/bronze/schema.yaml @@ -0,0 +1,83 @@ +version: 2 + +models: + + # Deputados DBT + + ## Bronze + - name: deputados + description: > + Tabela com informações básicas sobre deputados federais obtidas por meio da API de Dados Abertos da Câmara dos Deputados. + Contém dados cadastrais e institucionais como identificador, nome parlamentar, partido, unidade federativa, + legislatura e e-mail institucional. + Esta tabela representa a camada bronze do pipeline, aplicando padronização de tipos, + pequenas transformações e ajuste de fuso horário na data de ingestão. + Os dados são provenientes da fonte dados_abertos.deputados (camada raw) e + passam por conversão explícita de tipos para garantir consistência e rastreabilidade. + meta: + tags: + - bronze + columns: + - name: id + description: > + Identificador único do deputado na API da Câmara. + Utilizado como chave primária e referência para relacionamentos com outras tabelas. + + - name: nome + description: > + Nome parlamentar do deputado conforme registrado na Câmara dos Deputados. + + - name: siglapartido + description: > + Sigla do partido ao qual o deputado está filiado na legislatura informada. + + - name: siglauf + description: > + Sigla da Unidade Federativa (UF) que o deputado representa. + + - name: idlegislatura + description: > + Identificador numérico da legislatura à qual o deputado está vinculado. + + - name: email + description: > + Endereço de e-mail institucional do deputado na Câmara dos Deputados. + + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. + data_tests: + - verificacao_tipagem: + nome_tabela: 'deputados.deputados' + nome_coluna: 'id' + tipo_esperado: 'integer' + + - verificacao_tipagem: + nome_tabela: 'deputados.deputados' + nome_coluna: 'nome' + tipo_esperado: 'text' + + - verificacao_tipagem: + nome_tabela: 'deputados.deputados' + nome_coluna: 'siglapartido' + tipo_esperado: 'text' + + - verificacao_tipagem: + nome_tabela: 'deputados.deputados' + nome_coluna: 'siglauf' + tipo_esperado: 'text' + + - verificacao_tipagem: + nome_tabela: 'deputados.deputados' + nome_coluna: 'idlegislatura' + tipo_esperado: 'integer' + + - verificacao_tipagem: + nome_tabela: 'deputados.deputados' + nome_coluna: 'email' + tipo_esperado: 'text' + + - verificacao_tipagem: + nome_tabela: 'deputados.deputados' + nome_coluna: 'dt_ingest' + tipo_esperado: 'timestamp with time zone' \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/ipea/models/sources.yml b/airflow_lappis/dags/dbt/ipea/models/sources.yml index 6635fbf8..35a09ab9 100644 --- a/airflow_lappis/dags/dbt/ipea/models/sources.yml +++ b/airflow_lappis/dags/dbt/ipea/models/sources.yml @@ -48,3 +48,8 @@ sources: - name: estrutura_organizacional_cargos - name: cargos_funcao - name: unidade_organizacional + + - name: dados_abertos + schema: camara_deputados + tables: + - name: deputados From b93650d443590977e9dee5c60a51eb5d9c5bf5f8 Mon Sep 17 00:00:00 2001 From: Mateushqms Date: Wed, 4 Mar 2026 14:49:04 -0300 Subject: [PATCH 204/317] fix: ajustes na identacao e add url imagem --- .../models/deputados_dbt/bronze/deputados.sql | 1 + .../models/deputados_dbt/bronze/schema.yaml | 77 +++++++++++-------- 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/airflow_lappis/dags/dbt/ipea/models/deputados_dbt/bronze/deputados.sql b/airflow_lappis/dags/dbt/ipea/models/deputados_dbt/bronze/deputados.sql index ea893a30..24e0a1b3 100644 --- a/airflow_lappis/dags/dbt/ipea/models/deputados_dbt/bronze/deputados.sql +++ b/airflow_lappis/dags/dbt/ipea/models/deputados_dbt/bronze/deputados.sql @@ -9,6 +9,7 @@ with siglapartido::text as siglapartido, siglauf::text as siglauf, idlegislatura::integer as idlegislatura, + urlfoto::text as urlfoto, email::text as email, (dt_ingest || '-03:00')::timestamptz as dt_ingest from {{ source("dados_abertos", "deputados") }} diff --git a/airflow_lappis/dags/dbt/ipea/models/deputados_dbt/bronze/schema.yaml b/airflow_lappis/dags/dbt/ipea/models/deputados_dbt/bronze/schema.yaml index 9733b1c7..da1f6aef 100644 --- a/airflow_lappis/dags/dbt/ipea/models/deputados_dbt/bronze/schema.yaml +++ b/airflow_lappis/dags/dbt/ipea/models/deputados_dbt/bronze/schema.yaml @@ -39,6 +39,10 @@ models: description: > Identificador numérico da legislatura à qual o deputado está vinculado. + - name: urlfoto + description: > + URL da foto oficial do deputado disponibilizada pela Câmara dos Deputados. + - name: email description: > Endereço de e-mail institucional do deputado na Câmara dos Deputados. @@ -47,37 +51,42 @@ models: description: > Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. data_tests: - - verificacao_tipagem: - nome_tabela: 'deputados.deputados' - nome_coluna: 'id' - tipo_esperado: 'integer' - - - verificacao_tipagem: - nome_tabela: 'deputados.deputados' - nome_coluna: 'nome' - tipo_esperado: 'text' - - - verificacao_tipagem: - nome_tabela: 'deputados.deputados' - nome_coluna: 'siglapartido' - tipo_esperado: 'text' - - - verificacao_tipagem: - nome_tabela: 'deputados.deputados' - nome_coluna: 'siglauf' - tipo_esperado: 'text' - - - verificacao_tipagem: - nome_tabela: 'deputados.deputados' - nome_coluna: 'idlegislatura' - tipo_esperado: 'integer' - - - verificacao_tipagem: - nome_tabela: 'deputados.deputados' - nome_coluna: 'email' - tipo_esperado: 'text' - - - verificacao_tipagem: - nome_tabela: 'deputados.deputados' - nome_coluna: 'dt_ingest' - tipo_esperado: 'timestamp with time zone' \ No newline at end of file + - verificacao_tipagem: + nome_tabela: 'deputados.deputados' + nome_coluna: 'id' + tipo_esperado: 'integer' + + - verificacao_tipagem: + nome_tabela: 'deputados.deputados' + nome_coluna: 'nome' + tipo_esperado: 'text' + + - verificacao_tipagem: + nome_tabela: 'deputados.deputados' + nome_coluna: 'siglapartido' + tipo_esperado: 'text' + + - verificacao_tipagem: + nome_tabela: 'deputados.deputados' + nome_coluna: 'siglauf' + tipo_esperado: 'text' + + - verificacao_tipagem: + nome_tabela: 'deputados.deputados' + nome_coluna: 'idlegislatura' + tipo_esperado: 'integer' + + - verificacao_tipagem: + nome_tabela: 'deputados' + nome_coluna: 'urlfoto' + tipo_esperado: 'text' + + - verificacao_tipagem: + nome_tabela: 'deputados.deputados' + nome_coluna: 'email' + tipo_esperado: 'text' + + - verificacao_tipagem: + nome_tabela: 'deputados.deputados' + nome_coluna: 'dt_ingest' + tipo_esperado: 'timestamp with time zone' \ No newline at end of file From 5ddf37d39502e8642af3c2d3776b9c4a18d3f01c Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Wed, 4 Mar 2026 14:56:39 -0300 Subject: [PATCH 205/317] feat: modelagem bronze emendas --- airflow_lappis/dags/dbt/ipea/dbt_project.yml | 3 + .../models/emendas_dbt/bronze/programas.sql | 30 ++++ .../ipea/models/emendas_dbt/bronze/schema.yml | 138 ++++++++++++++++++ 3 files changed, 171 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/programas.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/schema.yml diff --git a/airflow_lappis/dags/dbt/ipea/dbt_project.yml b/airflow_lappis/dags/dbt/ipea/dbt_project.yml index 7bf6e704..9ea8a8f4 100755 --- a/airflow_lappis/dags/dbt/ipea/dbt_project.yml +++ b/airflow_lappis/dags/dbt/ipea/dbt_project.yml @@ -45,6 +45,9 @@ models: +schema: orcamento views: +materialized: view + emendas_dbt: + +materialized: table + +schema: emendas # siape_dbt: # +materialized: table # +schema: siape diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/programas.sql b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/programas.sql new file mode 100644 index 00000000..15c0357c --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/programas.sql @@ -0,0 +1,30 @@ +{{ config(materialized="table") }} + +with + programas_raw as ( + select + id_programa::integer as id_programa, + ano_programa::integer as ano_programa, + modalidade_programa::text as modalidade_programa, + codigo_programa::text as codigo_programa, + id_orgao_superior_programa:: integer as id_orgao_superior_programa, + sigla_orgao_programa::text as sigla_orgao_programa, + nome_orgao_programa::text as nome_orgao_programa, + id_unidade_gestora_programa::integer as id_unidade_gestora_programa, + documentos_origem_programa::text as documentos_origem_programa, + id_unidade_orcamentaria_responsavel_programa::integer as id_unidade_orcamentaria_responsavel_programa, + data_inicio_ciencia_programa::date as data_inicio_ciencia_programa, + data_fim_ciencia_programa::date as data_fim_ciencia_programa, + valor_necessidade_financeira_programa::numeric(15, 2) as valor_necessidade_financeira_programa, + valor_total_disponibilizado_programa::numeric(15, 2) as valor_total_disponibilizado_programa, + valor_impedido_programa::numeric(15, 2) as valor_impedido_programa, + valor_a_disponibilizar_programa::numeric(15, 2) as valor_a_disponibilizar_programa, + valor_documentos_habeis_gerados_programa::numeric(15, 2) as valor_documentos_habeis_gerados_programa, + valor_obs_geradas_programa::numeric(15, 2) as valor_obs_geradas_programa, + valor_disponibilidade_atual_programa::numeric(15, 2) as valor_disponibilidade_atual_programa, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("transferegov_emendas", "programas_especiais") }} + ) -- + +select * +from programas_raw diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/schema.yml b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/schema.yml new file mode 100644 index 00000000..1bb38d39 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/schema.yml @@ -0,0 +1,138 @@ +version: 2 + +models: + + # Emendas DBT + + ## Bronze + - name: programas + description: > + Tabela com informações sobre programas especiais do TransfereGov relacionados a emendas parlamentares. + Contém dados sobre a execução financeira dos programas, incluindo valores de necessidade financeira, + disponibilização, impedimentos e documentos hábeis gerados. + Os dados são extraídos da tabela programas_especiais do TransfereGov Emendas, com formatação adequada + de valores numéricos e datas. + meta: + tags: + - bronze + columns: + - name: id_programa + description: > + Identificador único do programa no TransfereGov. + - name: ano_programa + description: > + Ano de referência do programa. + - name: modalidade_programa + description: > + Modalidade do programa (ex: ESPECIAL). + - name: codigo_programa + description: > + Código identificador do programa no sistema. + - name: id_orgao_superior_programa + description: > + Identificador do órgão superior responsável pelo programa. + - name: sigla_orgao_programa + description: > + Sigla do órgão responsável pelo programa. + - name: nome_orgao_programa + description: > + Nome completo do órgão responsável pelo programa. + - name: id_unidade_gestora_programa + description: > + Identificador da unidade gestora responsável pelo programa. + - name: documentos_origem_programa + description: > + Documentos de origem associados ao programa. + - name: id_unidade_orcamentaria_responsavel_programa + description: > + Identificador da unidade orçamentária responsável pelo programa. + - name: data_inicio_ciencia_programa + description: > + Data de início de ciência do programa. + - name: data_fim_ciencia_programa + description: > + Data de fim de ciência do programa. + - name: valor_necessidade_financeira_programa + description: > + Valor da necessidade financeira do programa. + - name: valor_total_disponibilizado_programa + description: > + Valor total disponibilizado para o programa. + - name: valor_impedido_programa + description: > + Valor impedido do programa. + - name: valor_a_disponibilizar_programa + description: > + Valor ainda a ser disponibilizado para o programa. + - name: valor_documentos_habeis_gerados_programa + description: > + Valor dos documentos hábeis gerados para o programa. + - name: valor_obs_geradas_programa + description: > + Valor das ordens bancárias geradas para o programa. + - name: valor_disponibilidade_atual_programa + description: > + Valor da disponibilidade financeira atual do programa. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. + data_tests: + - row_count_match: + source_table: transferegov_emendas.programas_especiais + target_table: emendas.programas + - verificacao_tipagem: + nome_tabela: 'emendas.programas' + nome_coluna: 'id_programa' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.programas' + nome_coluna: 'ano_programa' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.programas' + nome_coluna: 'id_orgao_superior_programa' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.programas' + nome_coluna: 'id_unidade_gestora_programa' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.programas' + nome_coluna: 'id_unidade_orcamentaria_responsavel_programa' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.programas' + nome_coluna: 'data_inicio_ciencia_programa' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'emendas.programas' + nome_coluna: 'data_fim_ciencia_programa' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'emendas.programas' + nome_coluna: 'valor_necessidade_financeira_programa' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.programas' + nome_coluna: 'valor_total_disponibilizado_programa' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.programas' + nome_coluna: 'valor_impedido_programa' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.programas' + nome_coluna: 'valor_a_disponibilizar_programa' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.programas' + nome_coluna: 'valor_documentos_habeis_gerados_programa' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.programas' + nome_coluna: 'valor_obs_geradas_programa' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.programas' + nome_coluna: 'valor_disponibilidade_atual_programa' + tipo_esperado: 'numeric' From f3fdd7aad6dff0b83b07df2e90d2c60a471a2c11 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Wed, 4 Mar 2026 15:27:36 -0300 Subject: [PATCH 206/317] fix: sources missing transferegov_emendas --- airflow_lappis/dags/dbt/ipea/models/sources.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/airflow_lappis/dags/dbt/ipea/models/sources.yml b/airflow_lappis/dags/dbt/ipea/models/sources.yml index 6635fbf8..020ca8af 100644 --- a/airflow_lappis/dags/dbt/ipea/models/sources.yml +++ b/airflow_lappis/dags/dbt/ipea/models/sources.yml @@ -48,3 +48,9 @@ sources: - name: estrutura_organizacional_cargos - name: cargos_funcao - name: unidade_organizacional + + - name: transferegov_emendas + schema: transferegov_emendas + tables: + - name: programas_especiais + From b6d18692ed0281d728324bbd84c515af1c21a2dc Mon Sep 17 00:00:00 2001 From: cibelinda Date: Wed, 4 Mar 2026 17:12:52 -0300 Subject: [PATCH 207/317] =?UTF-8?q?feat:=20implementa=20ingest=C3=A3o=20de?= =?UTF-8?q?=20senadores?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dados_abertos/senadores_ingest_dag.py | 72 +++++++++++++++++++ airflow_lappis/plugins/cliente_senadores.py | 48 +++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/dados_abertos/senadores_ingest_dag.py create mode 100644 airflow_lappis/plugins/cliente_senadores.py diff --git a/airflow_lappis/dags/data_ingest/dados_abertos/senadores_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_abertos/senadores_ingest_dag.py new file mode 100644 index 00000000..3ecbf1ec --- /dev/null +++ b/airflow_lappis/dags/data_ingest/dados_abertos/senadores_ingest_dag.py @@ -0,0 +1,72 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule +from postgres_helpers import get_postgres_conn +from cliente_senadores import ClienteSenadores +from cliente_postgres import ClientPostgresDB + +@dag( + schedule_interval=get_dynamic_schedule("senadores_ingest_dag"), + start_date=datetime(2025, 1, 1), + catchup=False, + default_args={ + "owner": "Cibelly", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["senado_federal", "senadores", "dados_abertos"], +) +def senadores_ingest_dag() -> None: + """DAG para buscar e armazenar dados de senadores do Senado Federal.""" + + @task + def fetch_and_store_senadores() -> None: + logging.info("[senadores_ingest_dag.py] Iniciando extração de senadores") + + api = ClienteSenadores() + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + senadores_data = api.get_senadores_atuais() + + if senadores_data and len(senadores_data) > 0: + registros_limpos = [] + + for item in senadores_data: + info = item.get("IdentificacaoParlamentar", {}) + mandato = item.get("Mandato", {}) + + + senador_simplificado = { + "id": info.get("CodigoParlamentar"), + "nome_parlamentar": info.get("NomeParlamentar"), + "nome_completo": info.get("NomeCompletoParlamentar"), + "sexo": info.get("SexoParlamentar"), + "forma_tratamento": info.get("FormaTratamento"), + "url_foto": info.get("UrlFotoParlamentar"), + "url_pagina": info.get("UrlPaginaParlamentar"), + "email": info.get("EmailParlamentar"), + "sigla_partido": info.get("SiglaPartidoParlamentar"), + "uf": info.get("UfParlamentar"), + "id_legislatura": mandato.get("CodigoLegislatura"), + "dt_ingest": datetime.now().isoformat() + } + registros_limpos.append(senador_simplificado) + + logging.info( + f"[senadores_ingest_dag.py] Inserindo {len(registros_limpos)} " + f"senadores simplificados no schema senado_federal" + ) + + db.insert_data( + registros_limpos, + "senadores", + conflict_fields=["id"], + primary_key=["id"], + schema="senado_federal", + ) + + fetch_and_store_senadores() + +senadores_ingest_dag() \ No newline at end of file diff --git a/airflow_lappis/plugins/cliente_senadores.py b/airflow_lappis/plugins/cliente_senadores.py new file mode 100644 index 00000000..6a2cc748 --- /dev/null +++ b/airflow_lappis/plugins/cliente_senadores.py @@ -0,0 +1,48 @@ +import http +import logging +from typing import Any +from cliente_base import ClienteBase + +class ClienteSenadores(ClienteBase): + """ + Cliente para consumir a API de Dados Abertos do Senado Federal. + """ + + BASE_URL = "https://legis.senado.leg.br/dadosabertos" + BASE_HEADER = {"accept": "application/json"} + + def __init__(self) -> None: + super().__init__(base_url=ClienteSenadores.BASE_URL) + logging.info( + f"[cliente_senadores.py] Initialized ClienteSenadores em: {ClienteSenadores.BASE_URL}" + ) + + def get_senadores_atuais(self) -> list: + """ + Obtém a lista de senadores em exercício. + O Senado geralmente retorna tudo em uma única chamada, sem paginação complexa como a Câmara. + """ + endpoint = "/senador/lista/atual" + logging.info("[cliente_senadores.py] Fetching senadores atuais") + + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER + ) + + if status == http.HTTPStatus.OK and isinstance(data, dict): + # A estrutura do JSON do Senado é: ListaParlamentarEmExercicio -> Parlamentares -> Parlamentar + try: + lista_root = data.get("ListaParlamentarEmExercicio", {}) + parlamentares = lista_root.get("Parlamentares", {}).get("Parlamentar", []) + + if isinstance(parlamentares, dict): + parlamentares = [parlamentares] + + logging.info(f"[cliente_senadores.py] Successfully fetched {len(parlamentares)} senadores") + return parlamentares + except Exception as e: + logging.error(f"[cliente_senadores.py] Erro ao parsear JSON do Senado: {e}") + return [] + else: + logging.warning(f"[cliente_senadores.py] Failed with status: {status}") + return [] \ No newline at end of file From 38d79f5e9d3d5522396f61617a4d4f18ab7a9bbd Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Thu, 5 Mar 2026 15:19:36 -0300 Subject: [PATCH 208/317] style: aplicado lint senadores --- .../dados_abertos/senadores_ingest_dag.py | 13 +++++++------ airflow_lappis/plugins/cliente_senadores.py | 15 ++++++++++----- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/dados_abertos/senadores_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_abertos/senadores_ingest_dag.py index 3ecbf1ec..d1645cb4 100644 --- a/airflow_lappis/dags/data_ingest/dados_abertos/senadores_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_abertos/senadores_ingest_dag.py @@ -6,12 +6,13 @@ from cliente_senadores import ClienteSenadores from cliente_postgres import ClientPostgresDB + @dag( schedule_interval=get_dynamic_schedule("senadores_ingest_dag"), start_date=datetime(2025, 1, 1), catchup=False, default_args={ - "owner": "Cibelly", + "owner": "Cibelly", "retries": 1, "retry_delay": timedelta(minutes=5), }, @@ -32,12 +33,11 @@ def fetch_and_store_senadores() -> None: if senadores_data and len(senadores_data) > 0: registros_limpos = [] - + for item in senadores_data: info = item.get("IdentificacaoParlamentar", {}) mandato = item.get("Mandato", {}) - - + senador_simplificado = { "id": info.get("CodigoParlamentar"), "nome_parlamentar": info.get("NomeParlamentar"), @@ -50,7 +50,7 @@ def fetch_and_store_senadores() -> None: "sigla_partido": info.get("SiglaPartidoParlamentar"), "uf": info.get("UfParlamentar"), "id_legislatura": mandato.get("CodigoLegislatura"), - "dt_ingest": datetime.now().isoformat() + "dt_ingest": datetime.now().isoformat(), } registros_limpos.append(senador_simplificado) @@ -69,4 +69,5 @@ def fetch_and_store_senadores() -> None: fetch_and_store_senadores() -senadores_ingest_dag() \ No newline at end of file + +senadores_ingest_dag() diff --git a/airflow_lappis/plugins/cliente_senadores.py b/airflow_lappis/plugins/cliente_senadores.py index 6a2cc748..c58f59d3 100644 --- a/airflow_lappis/plugins/cliente_senadores.py +++ b/airflow_lappis/plugins/cliente_senadores.py @@ -3,6 +3,7 @@ from typing import Any from cliente_base import ClienteBase + class ClienteSenadores(ClienteBase): """ Cliente para consumir a API de Dados Abertos do Senado Federal. @@ -34,15 +35,19 @@ def get_senadores_atuais(self) -> list: try: lista_root = data.get("ListaParlamentarEmExercicio", {}) parlamentares = lista_root.get("Parlamentares", {}).get("Parlamentar", []) - + if isinstance(parlamentares, dict): parlamentares = [parlamentares] - - logging.info(f"[cliente_senadores.py] Successfully fetched {len(parlamentares)} senadores") + + logging.info( + f"[cliente_senadores.py] Successfully fetched {len(parlamentares)} senadores" + ) return parlamentares except Exception as e: - logging.error(f"[cliente_senadores.py] Erro ao parsear JSON do Senado: {e}") + logging.error( + f"[cliente_senadores.py] Erro ao parsear JSON do Senado: {e}" + ) return [] else: logging.warning(f"[cliente_senadores.py] Failed with status: {status}") - return [] \ No newline at end of file + return [] From 2ce2451e74dce0208edad6ab9c37df5fe60c47ba Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Thu, 5 Mar 2026 19:21:42 -0300 Subject: [PATCH 209/317] style: lint dags emendas --- .../finalidade_especial_ingest_dag.py | 15 +++++++++++---- .../ordem_bancaria_especial_ingest_dag.py | 1 + .../plugins/cliente_transferegov_emendas.py | 15 +++++++++------ 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/finalidade_especial_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/finalidade_especial_ingest_dag.py index d55cb174..00b6760c 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/finalidade_especial_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/finalidade_especial_ingest_dag.py @@ -48,10 +48,16 @@ def fetch_and_store_finalidade_especial() -> None: db.insert_data( finalidades_data, "finalidades_especiais", - conflict_fields=["id_executor", "cd_area_politica_publica_tipo_pt", - "area_politica_publica_pt"], - primary_key=["id_executor", "cd_area_politica_publica_tipo_pt", - "area_politica_publica_pt"], + conflict_fields=[ + "id_executor", + "cd_area_politica_publica_tipo_pt", + "area_politica_publica_pt", + ], + primary_key=[ + "id_executor", + "cd_area_politica_publica_tipo_pt", + "area_politica_publica_pt", + ], schema="transferegov_emendas", ) @@ -68,4 +74,5 @@ def fetch_and_store_finalidade_especial() -> None: fetch_and_store_finalidade_especial() + api_finalidade_especial_dag() diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/ordem_bancaria_especial_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/ordem_bancaria_especial_ingest_dag.py index 11918570..dde833a3 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/ordem_bancaria_especial_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/ordem_bancaria_especial_ingest_dag.py @@ -66,4 +66,5 @@ def fetch_and_store_ordem_bancaria_especial() -> None: fetch_and_store_ordem_bancaria_especial() + api_ordem_bancaria_especial_dag() diff --git a/airflow_lappis/plugins/cliente_transferegov_emendas.py b/airflow_lappis/plugins/cliente_transferegov_emendas.py index f9c5a173..e8297251 100644 --- a/airflow_lappis/plugins/cliente_transferegov_emendas.py +++ b/airflow_lappis/plugins/cliente_transferegov_emendas.py @@ -857,8 +857,7 @@ def get_all_finalidades_especiais(self, page_size: int = 1000) -> list: f"Total records: {len(all_data)}" ) return all_data - - + def get_ordens_bancarias_especiais( self, limit: int = 1000, offset: int = 0 ) -> Optional[list]: @@ -955,7 +954,9 @@ def get_all_ordens_bancarias_especiais(self, page_size: int = 1000) -> list: ) return all_data - def get_relatorio_gestao_novo_especial(self, limit: int = 1000, offset: int = 0) -> Optional[list]: + def get_relatorio_gestao_novo_especial( + self, limit: int = 1000, offset: int = 0 + ) -> Optional[list]: """ Obter relatórios de gestão novo especial com paginação. @@ -1048,7 +1049,7 @@ def get_all_relatorios_gestao_novo_especial(self, page_size: int = 1000) -> list f"Total records: {len(all_data)}" ) return all_data - + def get_plano_trabalho_especial( self, limit: int = 1000, offset: int = 0 ) -> Optional[list]: @@ -1144,7 +1145,9 @@ def get_all_plano_trabalho_especial(self, page_size: int = 1000) -> list: ) return all_data - def get_historico_pagamentos_especiais(self, limit: int = 1000, offset: int = 0) -> Optional[list]: + def get_historico_pagamentos_especiais( + self, limit: int = 1000, offset: int = 0 + ) -> Optional[list]: """ Obter histórico de pagamentos especiais com paginação. @@ -1236,4 +1239,4 @@ def get_all_historico_pagamentos_especiais(self, page_size: int = 1000) -> list: f"[cliente_transfere_gov.py] Extraction completed. " f"Total histórico de pagamentos especiais: {len(all_data)}" ) - return all_data \ No newline at end of file + return all_data From aac564f9f2807c6de57657321771486762c39b93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateus=20Henrique=20Queiroz=20Magalh=C3=A3es=20Sousa?= <163928182+Mateushqms@users.noreply.github.com> Date: Sat, 7 Mar 2026 19:38:17 -0300 Subject: [PATCH 210/317] Feat/camada bronze completa (#93) * feat: camadas bronze linha pagamentos e planos * feat: docs e tests de parte das emendas * feat: modelagem bronze para algumas das tabelas de emendas --------- Co-authored-by: Tiago Santos Bittencourt --- .../emendas_dbt/bronze/documentos_habeis.sql | 33 + .../emendas_dbt/bronze/empenhos_especiais.sql | 38 + .../models/emendas_dbt/bronze/executor.sql | 28 + .../models/emendas_dbt/bronze/finalidades.sql | 16 + .../bronze/historico_pagamentos.sql | 16 + .../ipea/models/emendas_dbt/bronze/metas.sql | 27 + .../emendas_dbt/bronze/ordens_bancarias.sql | 24 + .../emendas_dbt/bronze/planos_acoes.sql | 38 + .../bronze/planos_trabalho_especial.sql | 22 + .../emendas_dbt/bronze/relatorio_gestao.sql | 15 + .../bronze/relatorio_gestao_novo.sql | 18 + .../ipea/models/emendas_dbt/bronze/schema.yml | 909 +++++++++++++++++- .../dags/dbt/ipea/models/sources.yml | 11 + 13 files changed, 1194 insertions(+), 1 deletion(-) create mode 100644 airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/documentos_habeis.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/empenhos_especiais.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/executor.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/finalidades.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/historico_pagamentos.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/metas.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/ordens_bancarias.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/planos_acoes.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/planos_trabalho_especial.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/relatorio_gestao.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/relatorio_gestao_novo.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/documentos_habeis.sql b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/documentos_habeis.sql new file mode 100644 index 00000000..f9acd06c --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/documentos_habeis.sql @@ -0,0 +1,33 @@ +{{ config(materialized="table") }} + +with + documentos_habeis_raw as ( + select + id_dh::integer as id_dh, + id_empenho::integer as id_empenho, + numero_documento_habil::text as numero_documento_habil, + situacao_dh::integer as situacao_dh, + descricao_situacao_dh::text as descricao_situacao_dh, + tipo_documento_dh::text as tipo_documento_dh, + ug_emitente_dh::integer as ug_emitente_dh, + descricao_ug_emitente_dh::text as descricao_ug_emitente_dh, + data_vencimento_dh::date as data_vencimento_dh, + data_emissao_dh::date as data_emissao_dh, + ug_pagadora_dh::integer as ug_pagadora_dh, + descricao_ug_pagadora_dh::text as descricao_ug_pagadora_dh, + variacao_patrimonial_diminuta_dh::text as variacao_patrimonial_diminuta_dh, + passivo_transferencia_constitucional_legal_dh::text as passivo_transferencia_constitucional_legal_dh, + centro_custo_empenho::text as centro_custo_empenho, + codigo_siorg_empenho::integer as codigo_siorg_empenho, + mes_referencia_empenho::text as mes_referencia_empenho, + ano_referencia_empenho::integer as ano_referencia_empenho, + ug_beneficiada_dh::integer as ug_beneficiada_dh, + descricao_ug_beneficiada_dh::text as descricao_ug_beneficiada_dh, + valor_dh::numeric(15, 2) as valor_dh, + valor_rateio_dh::numeric(15, 2) as valor_rateio_dh, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("transferegov_emendas", "documentos_habeis_especiais") }} + ) -- + +select * +from documentos_habeis_raw diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/empenhos_especiais.sql b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/empenhos_especiais.sql new file mode 100644 index 00000000..62afcacd --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/empenhos_especiais.sql @@ -0,0 +1,38 @@ +{{ config(materialized="table") }} + +with + empenhos_raw as ( + select + id_empenho::integer as id_empenho, + id_minuta_empenho::text as id_minuta_empenho, + numero_empenho::text as numero_empenho, + situacao_empenho::integer as situacao_empenho, + descricao_situacao_empenho::text as descricao_situacao_empenho, + tipo_documento_empenho::integer as tipo_documento_empenho, + descricao_tipo_documento_empenho::text as descricao_tipo_documento_empenho, + status_processamento_empenho::text as status_processamento_empenho, + ug_responsavel_empenho::integer as ug_responsavel_empenho, + ug_emitente_empenho::integer as ug_emitente_empenho, + descricao_ug_emitente_empenho::text as descricao_ug_emitente_empenho, + fonte_recurso_empenho::text as fonte_recurso_empenho, + plano_interno_empenho::text as plano_interno_empenho, + ptres_empenho::numeric(15, 2) as ptres_empenho, -- verificar possibilidade de .0 + grupo_natureza_despesa_empenho::text as grupo_natureza_despesa_empenho, + natureza_despesa_empenho::text as natureza_despesa_empenho, + subitem_empenho::text as subitem_empenho, + categoria_despesa_empenho::text as categoria_despesa_empenho, + modalidade_despesa_empenho::integer as modalidade_despesa_empenho, + cnpj_beneficiario_empenho::text as cnpj_beneficiario_empenho, + nome_beneficiario_empenho::text as nome_beneficiario_empenho, + uf_beneficiario_empenho::text as uf_beneficiario_empenho, + numero_ro_empenho::text as numero_ro_empenho, + data_emissao_empenho::date as data_emissao_empenho, + prioridade_desbloqueio_empenho::integer as prioridade_desbloqueio_empenho, + valor_empenho::numeric(15, 2) as valor_empenho, + id_plano_acao::integer as id_plano_acao, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("transferegov_emendas", "empenhos_especiais") }} + ) -- + +select * +from empenhos_raw diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/executor.sql b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/executor.sql new file mode 100644 index 00000000..5ed46bee --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/executor.sql @@ -0,0 +1,28 @@ +{{ config(materialized="table") }} + +with + executor_especial_raw as ( + select + id_plano_acao::integer as id_plano_acao, + id_executor::integer as id_executor, + cnpj_executor::text as cnpj_executor, + nome_executor::text as nome_executor, + objeto_executor::text as objeto_executor, + vl_custeio_executor::numeric(15, 2) as valor_custeio_executor, + vl_investimento_executor::numeric(15, 2) as valor_investimento_executor, + ind_recursos_gerenciados_conta_especifica_executor::text as ind_recursos_gerenciados_conta_especifica_executor, + codigo_banco_executor::text as codigo_banco_executor, + nome_banco_executor::text as nome_banco_executor, + nullif(numero_agencia_executor, 'NaN')::numeric::integer as numero_agencia_executor, + dv_agencia_executor::text as dv_agencia_executor, + nome_agencia_executor::text as nome_agencia_executor, + numero_conta_executor::text as numero_conta_executor, + dv_conta_executor::text as dv_conta_executor, + codigo_situacao_dado_bancario_executor::integer as codigo_situacao_dado_bancario_executor, + descricao_situacao_dado_bancario_executor::text as descricao_situacao_dado_bancario_executor, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("transferegov_emendas", "executor_especial") }} + ) + +select * +from executor_especial_raw \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/finalidades.sql b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/finalidades.sql new file mode 100644 index 00000000..d1c494f5 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/finalidades.sql @@ -0,0 +1,16 @@ +{{ config(materialized="table") }} + +with + finalidade_especial_raw as ( + select + id_executor::integer as id_executor, + cd_area_politica_publica_tipo_pt::integer as cd_area_politica_publica_tipo_pt, + area_politica_publica_tipo_pt::text as area_politica_publica_tipo_pt, + cd_area_politica_publica_pt::integer as cd_area_politica_publica_pt, + area_politica_publica_pt::text as area_politica_publica_pt, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("transferegov_emendas", "finalidades_especiais") }} + ) + +select * +from finalidade_especial_raw \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/historico_pagamentos.sql b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/historico_pagamentos.sql new file mode 100644 index 00000000..e10a98fd --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/historico_pagamentos.sql @@ -0,0 +1,16 @@ +{{ config(materialized="table") }} + +with + historico_pagamentos_raw as ( + select + id_historico_op_ob::integer as id_historico_op_ob, + data_hora_historico_op::timestamp as data_hora_historico_op, + historico_situacao_op::integer as historico_situacao_op, + descricao_historico_situacao_op::text as descricao_historico_situacao_op, + id_op_ob::integer as id_op_ob, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("transferegov_emendas", "historico_pagamentos_especiais") }} + ) -- + +select * +from historico_pagamentos_raw diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/metas.sql b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/metas.sql new file mode 100644 index 00000000..580b4df8 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/metas.sql @@ -0,0 +1,27 @@ +{{ config(materialized="table") }} + +with + meta_especial_raw as ( + select + id_executor::integer as id_executor, + id_meta::integer as id_meta, + sequencial_meta::integer as sequencial_meta, + nome_meta::text as nome_meta, + desc_meta::text as desc_meta, + un_medida_meta::text as un_medida_meta, + qt_uniade_meta::numeric(15, 2) as qt_uniade_meta, + vl_custeio_emenda_especial_meta::numeric(15, 2) as valor_custeio_emenda_especial_meta, + vl_investimento_emenda_especial_meta::numeric(15, 2) as valor_investimento_emenda_especial_meta, + vl_custeio_recursos_proprios_meta::numeric(15, 2) as valor_custeio_recursos_proprios_meta, + vl_investimento_recursos_proprios_meta::numeric(15, 2) as valor_investimento_recursos_proprios_meta, + vl_custeio_rendimento_meta::numeric(15, 2) as valor_custeio_rendimento_meta, + vl_investimento_rendimento_meta::numeric(15, 2) as valor_investimento_rendimento_meta, + vl_custeio_doacao_meta::numeric(15, 2) as valor_custeio_doacao_meta, + vl_investimento_doacao_meta::numeric(15, 2) as valor_investimento_doacao_meta, + qt_meses_meta::integer as qt_meses_meta, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("transferegov_emendas", "metas_especiais") }} + ) + +select * +from meta_especial_raw \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/ordens_bancarias.sql b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/ordens_bancarias.sql new file mode 100644 index 00000000..8f0bd2c5 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/ordens_bancarias.sql @@ -0,0 +1,24 @@ +{{ config(materialized="table") }} + +with + ordens_bancarias_raw as ( + select + id_op_ob::integer as id_op_ob, + data_emissao_op::date as data_emissao_op, + numero_ordem_pagamento::text as numero_ordem_pagamento, + vinculacao_op::integer as vinculacao_op, + situacao_op::integer as situacao_op, + descricao_situacao_op::text as descricao_situacao_op, + data_situacao_op::date as data_situacao_op, + data_emissao_ob::date as data_emissao_ob, + numero_ordem_bancaria::text as numero_ordem_bancaria, + numero_ordem_lancamento::text as numero_ordem_lancamento, + data_assinatura_ordenador_despesa_ob::date as data_assinatura_ordenador_despesa_ob, + data_assinatura_gestor_financeiro_ob::date as data_assinatura_gestor_financeiro_ob, + id_dh::integer as id_dh, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("transferegov_emendas", "ordens_bancarias_especiais") }} + ) -- + +select * +from ordens_bancarias_raw diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/planos_acoes.sql b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/planos_acoes.sql new file mode 100644 index 00000000..74b72cef --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/planos_acoes.sql @@ -0,0 +1,38 @@ +{{ config(materialized="table") }} + +with + planos_raw as ( + select + id_plano_acao::integer as id_plano_acao, + codigo_plano_acao::text as codigo_plano_acao, + ano_plano_acao::integer as ano_plano_acao, + modalidade_plano_acao::text as modalidade_plano_acao, + situacao_plano_acao::text as situacao_plano_acao, + cnpj_beneficiario_plano_acao::text as cnpj_beneficiario_plano_acao, + nome_beneficiario_plano_acao::text as nome_beneficiario_plano_acao, + uf_beneficiario_plano_acao::text as uf_beneficiario_plano_acao, + codigo_banco_plano_acao::text as codigo_banco_plano_acao, + NULLIF(codigo_situacao_dado_bancario_plano_acao::numeric, 'NaN'::numeric)::integer as codigo_situacao_dado_bancario_plano_acao, + nome_banco_plano_acao::text as nome_banco_plano_acao, + NULLIF(numero_agencia_plano_acao::numeric, 'NaN'::numeric)::integer as numero_agencia_plano_acao, + dv_agencia_plano_acao::text as dv_agencia_plano_acao, + NULLIF(numero_conta_plano_acao::numeric, 'NaN'::numeric)::integer as numero_conta_plano_acao, + dv_conta_plano_acao::text as dv_conta_plano_acao, + nome_parlamentar_emenda_plano_acao::text as nome_parlamentar_emenda_plano_acao, + ano_emenda_parlamentar_plano_acao::text as ano_emenda_parlamentar_plano_acao, + codigo_parlamentar_emenda_plano_acao::text as codigo_parlamentar_emenda_plano_acao, + sequencial_emenda_parlamentar_plano_acao::integer as sequencial_emenda_parlamentar_plano_acao, + numero_emenda_parlamentar_plano_acao::text as numero_emenda_parlamentar_plano_acao, + codigo_emenda_parlamentar_formatado_plano_acao::text as codigo_emenda_parlamentar_formatado_plano_acao, + codigo_descricao_areas_politicas_publicas_plano_acao::text as codigo_descricao_areas_politicas_publicas_plano_acao, + descricao_programacao_orcamentaria_plano_acao::text as descricao_programacao_orcamentaria_plano_acao, + motivo_impedimento_plano_acao::text as motivo_impedimento_plano_acao, + valor_custeio_plano_acao::numeric(15, 2) as valor_custeio_plano_acao, + valor_investimento_plano_acao::numeric(15, 2) as valor_investimento_plano_acao, + id_programa::integer as id_programa, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("transferegov_emendas", "planos_acao_especiais") }} + ) -- + +select * +from planos_raw diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/planos_trabalho_especial.sql b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/planos_trabalho_especial.sql new file mode 100644 index 00000000..30a38a1b --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/planos_trabalho_especial.sql @@ -0,0 +1,22 @@ +{{ config(materialized="table") }} + +with + planos_trabalho_raw as ( + select + id_plano_trabalho::integer as id_plano_trabalho, + situacao_plano_trabalho::text as situacao_plano_trabalho, + ind_orcamento_proprio_plano_trabalho::text as ind_orcamento_proprio_plano_trabalho, + data_inicio_execucao_plano_trabalho::timestamp as data_inicio_execucao_plano_trabalho, + data_fim_execucao_plano_trabalho::timestamp as data_fim_execucao_plano_trabalho, + prazo_execucao_meses_plano_trabalho::integer as prazo_execucao_meses_plano_trabalho, + id_plano_acao::integer as id_plano_acao, + classificacao_orcamentaria_pt::text as classificacao_orcamentaria_pt, + ind_justificativa_prorrogacao_atraso_pt::boolean as ind_justificativa_prorrogacao_atraso_pt, + ind_justificativa_prorrogacao_paralizacao_pt::boolean as ind_justificativa_prorrogacao_paralizacao_pt, + justificativa_prorrogacao_pt::text as justificativa_prorrogacao_pt, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("transferegov_emendas", "plano_trabalho_especial") }} + ) -- + +select * +from planos_trabalho_raw diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/relatorio_gestao.sql b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/relatorio_gestao.sql new file mode 100644 index 00000000..b9714838 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/relatorio_gestao.sql @@ -0,0 +1,15 @@ +{{ config(materialized="table") }} + +with + relatorio_gestao_raw as ( + select + id_relatorio_gestao::integer as id_relatorio_gestao, + situacao_relatorio_gestao::text as situacao_relatorio_gestao, + parecer_relatorio_gestao::text as parecer_relatorio_gestao, + id_plano_acao::integer as id_plano_acao, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("transferegov_emendas", "relatorio_gestao_especial") }} + ) + +select * +from relatorio_gestao_raw \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/relatorio_gestao_novo.sql b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/relatorio_gestao_novo.sql new file mode 100644 index 00000000..06d5cf48 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/relatorio_gestao_novo.sql @@ -0,0 +1,18 @@ +{{ config(materialized="table") }} + +with + relatorio_gestao_novo_raw as ( + select + id_relatorio_gestao_novo::integer as id_relatorio_gestao_novo, + data_e_hora_relatorio_gestao_novo::timestamp as data_e_hora_relatorio_gestao_novo, + tipo_relatorio_gestao_novo::text as tipo_relatorio_gestao_novo, + valor_executado_relatorio_gestao_novo::numeric(15, 2) as valor_executado_relatorio_gestao_novo, + valor_pendente_relatorio_gestao_novo::numeric(15, 2) as valor_pendente_relatorio_gestao_novo, + situacao_relatorio_gestao_novo::text as situacao_relatorio_gestao_novo, + id_plano_acao::integer as id_plano_acao, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("transferegov_emendas", "relatorios_gestao_novo_especial") }} + ) + +select * +from relatorio_gestao_novo_raw \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/schema.yml b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/schema.yml index 1bb38d39..3decb28a 100644 --- a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/schema.yml @@ -76,7 +76,7 @@ models: - name: dt_ingest description: > Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. - data_tests: + tests: - row_count_match: source_table: transferegov_emendas.programas_especiais target_table: emendas.programas @@ -136,3 +136,910 @@ models: nome_tabela: 'emendas.programas' nome_coluna: 'valor_disponibilidade_atual_programa' tipo_esperado: 'numeric' + + - name: planos_acoes + description: > + Tabela com informações sobre os planos de ação especiais relacionados a emendas parlamentares do TransfereGov. + Contém dados sobre beneficiários, dados bancários, emendas parlamentares, classificação orçamentária, + impedimentos e valores de custeio e investimento. + Os dados são extraídos da tabela planos_acao_especiais do TransfereGov Emendas, com formatação adequada + de valores numéricos. + meta: + tags: + - bronze + columns: + - name: id_plano_acao + description: > + Identificador único do Plano de Ação (PA). + - name: codigo_plano_acao + description: > + Código do Programa concatenado com o ID do Plano de Ação. + - name: ano_plano_acao + description: > + Ano de criação do Plano de Ação. + - name: modalidade_plano_acao + description: > + Modalidade de Transferência do Plano de Ação. + - name: situacao_plano_acao + description: > + Situação do Plano de Ação. + - name: cnpj_beneficiario_plano_acao + description: > + CNPJ – Cadastro Nacional de Pessoa Jurídica do Beneficiário do Plano de Ação. + - name: nome_beneficiario_plano_acao + description: > + Nome do Beneficiário do Plano de Ação. + - name: uf_beneficiario_plano_acao + description: > + Sigla da Unidade de Federação do beneficiário. + - name: codigo_banco_plano_acao + description: > + Código do Banco do PA. + - name: codigo_situacao_dado_bancario_plano_acao + description: > + Código da Situação da Conta Corrente do PA. + - name: nome_banco_plano_acao + description: > + Nome do Banco do PA. + - name: numero_agencia_plano_acao + description: > + Número da Agência Bancária da Conta Corrente do PA. + - name: dv_agencia_plano_acao + description: > + Dígito Verificador da Agência Bancária da Conta Corrente do PA. + - name: numero_conta_plano_acao + description: > + Número da Conta Corrente do PA. + - name: dv_conta_plano_acao + description: > + Dígito Verificador da Conta Corrente do PA. + - name: nome_parlamentar_emenda_plano_acao + description: > + Nome do Parlamentar Autor da Emenda. + - name: ano_emenda_parlamentar_plano_acao + description: > + Ano da Emenda Parlamentar. + - name: codigo_parlamentar_emenda_plano_acao + description: > + Código do Parlamentar Autor da Emenda. + - name: sequencial_emenda_parlamentar_plano_acao + description: > + Sequencial da Emenda por Parlamentar no Ano. + - name: numero_emenda_parlamentar_plano_acao + description: > + Concatenação do Ano, Código e Sequencial do Parlamentar. + - name: codigo_emenda_parlamentar_formatado_plano_acao + description: > + Código Formatado da Emenda Parlamentar. + - name: codigo_descricao_areas_politicas_publicas_plano_acao + description: > + Concatenação dos Códigos e Descrições dos Tipos da Áreas das Políticas Públicas + com os Códigos e Descrições das Áreas das Políticas Públicas. + - name: descricao_programacao_orcamentaria_plano_acao + description: > + Concatenação das Programações Orçamentárias constantes da Lei Orçamentária do ente + beneficiado na qual o recurso será apropriado. + - name: motivo_impedimento_plano_acao + description: > + Motivo do Impedimento do Plano de Ação. + - name: valor_custeio_plano_acao + description: > + Valor Consolidado de Custeio das Emendas Parlamentares do Plano de Ação. + - name: valor_investimento_plano_acao + description: > + Valor Consolidado de Investimento das Emendas Parlamentares do Plano de Ação. + - name: id_programa + description: > + Identificador único do Programa associado ao Plano de Ação. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. + tests: + - row_count_match: + source_table: transferegov_emendas.planos_acao_especiais + target_table: emendas.planos_acoes + - verificacao_tipagem: + nome_tabela: 'emendas.planos_acoes' + nome_coluna: 'id_plano_acao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.planos_acoes' + nome_coluna: 'ano_plano_acao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.planos_acoes' + nome_coluna: 'codigo_situacao_dado_bancario_plano_acao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.planos_acoes' + nome_coluna: 'numero_agencia_plano_acao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.planos_acoes' + nome_coluna: 'numero_conta_plano_acao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.planos_acoes' + nome_coluna: 'sequencial_emenda_parlamentar_plano_acao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.planos_acoes' + nome_coluna: 'valor_custeio_plano_acao' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.planos_acoes' + nome_coluna: 'valor_investimento_plano_acao' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.planos_acoes' + nome_coluna: 'id_programa' + tipo_esperado: 'integer' + + - name: empenhos_emendas + description: > + Tabela com informações sobre empenhos (Notas de Empenho) relacionados a emendas parlamentares do TransfereGov. + Contém dados sobre a execução orçamentária dos empenhos, incluindo valores, beneficiários, + classificação orçamentária e situação. + Os dados são extraídos da tabela empenhos_especiais do TransfereGov Emendas, com formatação adequada + de valores numéricos e datas. + meta: + tags: + - bronze + columns: + - name: id_empenho + description: > + Identificador único da Nota de Empenho (NE). + - name: id_minuta_empenho + description: > + Número da Minuta gerado para Nota de Empenho, utiliza o Número Interno e Ano de Emissão. + - name: numero_empenho + description: > + Número da Nota de Empenho gerada e enviada pelo SIAFI (Sistema Integrado de Administração Financeira). + - name: situacao_empenho + description: > + Código da situação da Nota de Empenho. + - name: descricao_situacao_empenho + description: > + Descrição da situação da Nota de Empenho. + - name: tipo_documento_empenho + description: > + Código do tipo da Nota de Empenho. + - name: descricao_tipo_documento_empenho + description: > + Descrição do tipo da Nota de Empenho. + - name: status_processamento_empenho + description: > + Indica o status do processamento em lote da Nota de Empenho. + - name: ug_responsavel_empenho + description: > + Código da Unidade Gestora Responsável da Nota de Empenho. + - name: ug_emitente_empenho + description: > + Código da Unidade Gestora Emitente da Nota de Empenho. + - name: descricao_ug_emitente_empenho + description: > + Nome da Unidade Gestora Emitente da Nota de Empenho. + - name: fonte_recurso_empenho + description: > + Fonte de Recurso da Nota de Empenho no SIAFI (Sistema Integrado de Administração Financeira). + - name: plano_interno_empenho + description: > + Instrumento de planejamento e de acompanhamento da ação planejada, usado como forma de detalhamento desta, + de uso exclusivo de cada Ministério/órgão. Código com até 11 posições alfa-numéricas. + - name: ptres_empenho + description: > + Número do Programa de Trabalho Resumido. + - name: grupo_natureza_despesa_empenho + description: > + Primeiro dígito do Código da Natureza de Despesa no SIAFI. + - name: natureza_despesa_empenho + description: > + Código da Natureza de Despesa no SIAFI (Sistema Integrado de Administração Financeira). + - name: subitem_empenho + description: > + Valor do Sub-item da Natureza de Despesa no SIAFI (Sistema Integrado de Administração Financeira). + - name: categoria_despesa_empenho + description: > + Código da Categoria de Despesa associada à Nota de Empenho. + - name: modalidade_despesa_empenho + description: > + Código da Modalidade de Despesa. + - name: cnpj_beneficiario_empenho + description: > + CNPJ do beneficiário da Nota de Empenho. + - name: nome_beneficiario_empenho + description: > + Nome do beneficiário da Nota de Empenho. + - name: uf_beneficiario_empenho + description: > + Sigla da Unidade da Federação do beneficiário. + - name: numero_ro_empenho + description: > + Número da lista gerado e enviado pelo SIAFI (Sistema Integrado de Administração Financeira). + - name: data_emissao_empenho + description: > + Data de envio ao SIAFI (Sistema Integrado de Administração Financeira), convertida para formato de data padrão. + - name: prioridade_desbloqueio_empenho + description: > + Indicador de prioridade no desbloqueio de recursos. + - name: valor_empenho + description: > + Valor total da Nota de Empenho, formatado como numérico. + - name: id_plano_acao + description: > + Identificador único do Plano de Ação (PA) relacionado ao empenho. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. + tests: + - row_count_match: + source_table: transferegov_emendas.empenhos_especiais + target_table: emendas.empenhos_emendas + - verificacao_tipagem: + nome_tabela: 'emendas.empenhos_emendas' + nome_coluna: 'id_empenho' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.empenhos_emendas' + nome_coluna: 'situacao_empenho' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.empenhos_emendas' + nome_coluna: 'tipo_documento_empenho' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.empenhos_emendas' + nome_coluna: 'ug_responsavel_empenho' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.empenhos_emendas' + nome_coluna: 'ug_emitente_empenho' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.empenhos_emendas' + nome_coluna: 'ptres_empenho' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.empenhos_emendas' + nome_coluna: 'modalidade_despesa_empenho' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.empenhos_emendas' + nome_coluna: 'data_emissao_empenho' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'emendas.empenhos_emendas' + nome_coluna: 'prioridade_desbloqueio_empenho' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.empenhos_emendas' + nome_coluna: 'valor_empenho' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.empenhos_emendas' + nome_coluna: 'id_plano_acao' + tipo_esperado: 'integer' + + - name: ordens_bancarias + description: > + Tabela com informações sobre ordens de pagamento e ordens bancárias relacionadas a emendas parlamentares + do TransfereGov. Contém dados sobre a emissão, situação e assinaturas das ordens de pagamento e bancárias, + incluindo vinculação ao SIAFI e referência ao documento hábil de origem. + Os dados são extraídos da tabela ordem_pagamento_ordem_bancaria_especial do TransfereGov Emendas, + com formatação adequada de datas. + meta: + tags: + - bronze + columns: + - name: id_op_ob + description: > + Identificador único da Operação de Ordem Bancária. + - name: data_emissao_op + description: > + Data da emissão da Ordem de Pagamento. + - name: numero_ordem_pagamento + description: > + Número da Ordem de Pagamento, no formato AAAAOPNNNNNN (ex: "2020OP146800"). + - name: vinculacao_op + description: > + Código da vinculação da Ordem de Pagamento no SIAFI (Padrão: 405). + - name: situacao_op + description: > + Código da situação da Ordem de Pagamento/Bancária. + - name: descricao_situacao_op + description: > + Descrição da situação da Ordem de Pagamento/Bancária. + - name: data_situacao_op + description: > + Data da situação da Ordem de Pagamento/Bancária. + - name: data_emissao_ob + description: > + Data da emissão da Ordem Bancária no SIAFI. + - name: numero_ordem_bancaria + description: > + Número da Ordem Bancária, no formato AAAAOBNNNNNN (ex: "2020OB146800"). + - name: numero_ordem_lancamento + description: > + Número da Nota de Lançamento no sistema, no formato AAAANSNNNNNN (ex: "2020NS146800"). + - name: data_assinatura_ordenador_despesa_ob + description: > + Data de assinatura do Ordenador de Despesa. + - name: data_assinatura_gestor_financeiro_ob + description: > + Data de assinatura do Gestor Financeiro. + - name: id_dh + description: > + Identificador único do Documento Hábil (DH) vinculado à ordem bancária. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. + tests: + - row_count_match: + source_table: transferegov_emendas.ordens_bancarias_especiais + target_table: emendas.ordens_bancarias + - verificacao_tipagem: + nome_tabela: 'emendas.ordens_bancarias' + nome_coluna: 'id_op_ob' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.ordens_bancarias' + nome_coluna: 'data_emissao_op' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'emendas.ordens_bancarias' + nome_coluna: 'vinculacao_op' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.ordens_bancarias' + nome_coluna: 'situacao_op' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.ordens_bancarias' + nome_coluna: 'data_situacao_op' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'emendas.ordens_bancarias' + nome_coluna: 'data_emissao_ob' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'emendas.ordens_bancarias' + nome_coluna: 'data_assinatura_ordenador_despesa_ob' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'emendas.ordens_bancarias' + nome_coluna: 'data_assinatura_gestor_financeiro_ob' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'emendas.ordens_bancarias' + nome_coluna: 'id_dh' + tipo_esperado: 'integer' + + - name: historico_pagamentos + description: > + Tabela com informações sobre o histórico de eventos das ordens de pagamento relacionadas a emendas + parlamentares do TransfereGov. Contém dados sobre os eventos (situações) ocorridos ao longo do ciclo + de vida de cada ordem de pagamento, incluindo data/hora da ocorrência e descrição do evento. + Os dados são extraídos da tabela historico_pagamento_especial do TransfereGov Emendas. + meta: + tags: + - bronze + columns: + - name: id_historico_op_ob + description: > + Identificador único do registro de histórico da ordem de pagamento/bancária. + - name: data_hora_historico_op + description: > + Data e hora da inserção ou atualização do registro, conforme hora do sistema. + - name: historico_situacao_op + description: > + Código do evento da Ordem de Pagamento. + - name: descricao_historico_situacao_op + description: > + Descrição do evento da Ordem de Pagamento. + - name: id_op_ob + description: > + Identificador único da Operação de Ordem Bancária associada ao histórico. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. + tests: + - row_count_match: + source_table: transferegov_emendas.historico_pagamentos_especiais + target_table: emendas.historico_pagamentos + - verificacao_tipagem: + nome_tabela: 'emendas.historico_pagamentos' + nome_coluna: 'id_historico_op_ob' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.historico_pagamentos' + nome_coluna: 'data_hora_historico_op' + tipo_esperado: 'timestamp without time zone' + - verificacao_tipagem: + nome_tabela: 'emendas.historico_pagamentos' + nome_coluna: 'historico_situacao_op' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.historico_pagamentos' + nome_coluna: 'id_op_ob' + tipo_esperado: 'integer' + + - name: planos_trabalho_especial + description: > + Tabela com informações sobre os planos de trabalho especiais relacionados a emendas parlamentares + do TransfereGov. Contém dados sobre o planejamento de execução, incluindo situação, prazos, + indicadores de orçamento próprio, classificação orçamentária e justificativas de prorrogação. + Os dados são extraídos da tabela plano_trabalho_especial do TransfereGov Emendas. + meta: + tags: + - bronze + columns: + - name: id_plano_trabalho + description: > + Identificador único do Planejamento. + - name: situacao_plano_trabalho + description: > + Identificador da situação do Planejamento. + - name: ind_orcamento_proprio_plano_trabalho + description: > + Indicador de que os recursos do Plano de Ação foram indicados no orçamento próprio do Beneficiário. + - name: data_inicio_execucao_plano_trabalho + description: > + Data de início da execução do Plano de Trabalho Especial. + - name: data_fim_execucao_plano_trabalho + description: > + Data prevista para o fim da execução do Plano de Trabalho Especial com base no prazo de execução. + - name: prazo_execucao_meses_plano_trabalho + description: > + Prazo de execução em meses. + - name: id_plano_acao + description: > + Identificador do Plano de Ação da Proposta. + - name: classificacao_orcamentaria_pt + description: > + Texto com classificação orçamentária da despesa informado quando indicado no orçamento próprio. + - name: ind_justificativa_prorrogacao_atraso_pt + description: > + Indica atraso na liberação dos recursos. + - name: ind_justificativa_prorrogacao_paralizacao_pt + description: > + Indica paralização da execução do objeto. + - name: justificativa_prorrogacao_pt + description: > + Detalhamento da justificativa da prorrogação de prazo. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. + tests: + - row_count_match: + source_table: transferegov_emendas.plano_trabalho_especial + target_table: emendas.planos_trabalho_especial + - verificacao_tipagem: + nome_tabela: 'emendas.planos_trabalho_especial' + nome_coluna: 'id_plano_trabalho' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.planos_trabalho_especial' + nome_coluna: 'data_inicio_execucao_plano_trabalho' + tipo_esperado: 'timestamp without time zone' + - verificacao_tipagem: + nome_tabela: 'emendas.planos_trabalho_especial' + nome_coluna: 'data_fim_execucao_plano_trabalho' + tipo_esperado: 'timestamp without time zone' + - verificacao_tipagem: + nome_tabela: 'emendas.planos_trabalho_especial' + nome_coluna: 'prazo_execucao_meses_plano_trabalho' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.planos_trabalho_especial' + nome_coluna: 'id_plano_acao' + tipo_esperado: 'integer' + + - name: documentos_habeis + description: > + Tabela com informações sobre documentos hábeis relacionados a emendas parlamentares do TransfereGov. + Contém dados sobre a emissão, situação, unidades gestoras envolvidas, valores e referências + orçamentárias dos documentos hábeis vinculados aos empenhos. + Os dados são extraídos da tabela documentos_habeis_especiais do TransfereGov Emendas, + com formatação adequada de valores numéricos e datas. + meta: + tags: + - bronze + columns: + - name: id_dh + description: > + Identificador único do Documento Hábil (DH). + - name: id_empenho + description: > + Identificador único da Nota de Empenho (NE) vinculada ao documento hábil. + - name: numero_documento_habil + description: > + Número do DH no formato YYYYTTNNNNNN (ex: "2020TF000001"). + - name: situacao_dh + description: > + Código da situação do Documento Hábil. + - name: descricao_situacao_dh + description: > + Descrição da situação do Documento Hábil. + - name: tipo_documento_dh + description: > + Código do tipo do Documento Hábil. + - name: ug_emitente_dh + description: > + Código da Unidade Gestora Emitente do Documento Hábil. + - name: descricao_ug_emitente_dh + description: > + Nome da Unidade Gestora Emitente do Documento Hábil. + - name: data_vencimento_dh + description: > + Data de vencimento do Documento Hábil. + - name: data_emissao_dh + description: > + Data de emissão do Documento Hábil. + - name: ug_pagadora_dh + description: > + Código da Unidade de Gestão Pagadora do Documento Hábil. + - name: descricao_ug_pagadora_dh + description: > + Nome da Unidade de Gestão Pagadora do Documento Hábil. + - name: variacao_patrimonial_diminuta_dh + description: > + Variação Patrimonial Diminutiva. + - name: passivo_transferencia_constitucional_legal_dh + description: > + Passivo de Transferência Legal ou Constitucional. + - name: centro_custo_empenho + description: > + Código do Centro de Custo. + - name: codigo_siorg_empenho + description: > + Código SIORG do Centro de Custo. + - name: mes_referencia_empenho + description: > + Mês de referência do Centro de Custo. + - name: ano_referencia_empenho + description: > + Ano de referência do Centro de Custo. + - name: ug_beneficiada_dh + description: > + Código da Unidade Gestora Beneficiada do Documento Hábil. + - name: descricao_ug_beneficiada_dh + description: > + Nome da Unidade Gestora Beneficiada do Documento Hábil. + - name: valor_dh + description: > + Valor do Documento Hábil. Se a Disponibilidade Financeira for menor que o valor do Empenho, + mais de um Documento Hábil pode ser criado. + - name: valor_rateio_dh + description: > + Valor do Rateio. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. + tests: + - row_count_match: + source_table: transferegov_emendas.documentos_habeis_especiais + target_table: emendas.documentos_habeis + - verificacao_tipagem: + nome_tabela: 'emendas.documentos_habeis' + nome_coluna: 'id_dh' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.documentos_habeis' + nome_coluna: 'id_empenho' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.documentos_habeis' + nome_coluna: 'situacao_dh' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.documentos_habeis' + nome_coluna: 'ug_emitente_dh' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.documentos_habeis' + nome_coluna: 'data_vencimento_dh' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'emendas.documentos_habeis' + nome_coluna: 'data_emissao_dh' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'emendas.documentos_habeis' + nome_coluna: 'ug_pagadora_dh' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.documentos_habeis' + nome_coluna: 'codigo_siorg_empenho' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.documentos_habeis' + nome_coluna: 'ano_referencia_empenho' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.documentos_habeis' + nome_coluna: 'ug_beneficiada_dh' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.documentos_habeis' + nome_coluna: 'valor_dh' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.documentos_habeis' + nome_coluna: 'valor_rateio_dh' + tipo_esperado: 'numeric' + + - name: relatorio_gestao + description: > + Tabela que armazena a situação e o parecer final do Relatório de Gestão + vinculado ao Plano de Ação das emendas especiais. + meta: + tags: + - bronze + columns: + - name: id_relatorio_gestao + description: "Identificador Único do Relatorio de Gestão." + - name: situacao_relatorio_gestao + description: "Situação do Relatório de Gestão" + - name: parecer_relatorio_gestao + description: "Parecer do Relatório de Gestão" + - name: id_plano_acao + description: "Identificador Único do Plano de Ação (PA)." + data_tests: + - verificacao_tipagem: + nome_tabela: 'emendas.relatorio_gestao' + nome_coluna: 'id_relatorio_gestao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.relatorio_gestao' + nome_coluna: 'id_plano_acao' + tipo_esperado: 'integer' + + - name: relatorio_gestao_novo + description: > + Tabela que armazena os registros do Novo Relatório de Gestão das Emendas Especiais. + Contém o detalhamento da execução financeira por Plano de Ação, incluindo valores + executados, pendentes e o status de tramitação do relatório. + meta: + tags: + - bronze + columns: + - name: id_relatorio_gestao_novo + description: "Identificador único do registro do novo relatório de gestão." + - name: data_e_hora_relatorio_gestao_novo + description: "Timestamp do momento do registro ou última atualização do relatório." + - name: tipo_relatorio_gestao_novo + description: "Classificação do tipo de relatório enviado pelo executor." + - name: valor_executado_relatorio_gestao_novo + description: "Montante financeiro total que já foi executado e declarado." + - name: valor_pendente_relatorio_gestao_novo + description: "Montante financeiro que ainda aguarda execução ou comprovação." + - name: situacao_relatorio_gestao_novo + description: "Status atual do relatório (ex: Em preenchimento, Enviado, Homologado)." + - name: id_plano_acao + description: "Chave estrangeira que vincula o relatório ao Plano de Ação correspondente." + - name: dt_ingest + description: "Data e hora da ingestão dos dados na camada bronze (UTC-3)." + data_tests: + - row_count_match: + source_table: transferegov_emendas.relatorio_gestao_novo_especial + target_table: emendas.relatorio_gestao_novo + - verificacao_tipagem: + nome_tabela: 'emendas.relatorio_gestao_novo' + nome_coluna: 'id_relatorio_gestao_novo' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.relatorio_gestao_novo' + nome_coluna: 'data_e_hora_relatorio_gestao_novo' + tipo_esperado: 'timestamp without time zone' + - verificacao_tipagem: + nome_tabela: 'emendas.relatorio_gestao_novo' + nome_coluna: 'valor_executado_relatorio_gestao_novo' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.relatorio_gestao_novo' + nome_coluna: 'valor_pendente_relatorio_gestao_novo' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.relatorio_gestao_novo' + nome_coluna: 'id_plano_acao' + tipo_esperado: 'integer' + + - name: executor_especial + description: > + Tabela com informações detalhadas sobre os executores dos planos de ação das emendas. + Contém dados cadastrais do executor, detalhes do objeto da execução, valores financeiros de custeio + e investimento, além de informações bancárias para a gestão dos recursos. + Os dados são oriundos da API 'executor_especial' do Transferegov. + meta: + tags: + - bronze + columns: + - name: id_plano_acao + description: "Identificador único do Plano de Ação da Proposta." + - name: id_executor + description: "Identificador Único do Executor do Planejamento." + - name: cnpj_executor + description: "CNPJ do ente ou entidade executora." + - name: nome_executor + description: "Nome do Executor." + - name: objeto_executor + description: "Texto detalhado informando o Objeto da Execução." + - name: valor_custeio_executor + description: "Valor de Custeio do objeto a ser atendido pelo executor." + - name: valor_investimento_executor + description: "Valor de Investimento do objeto a ser atendido pelo executor." + - name: ind_recursos_gerenciados_conta_especifica_executor + description: "Indica se os recursos serão gerenciados por conta específica do executor." + - name: codigo_banco_executor + description: "Código da instituição bancária do Executor." + - name: nome_banco_executor + description: "Nome do Banco do Executor." + - name: numero_agencia_executor + description: "Número da Agência Bancária do Executor." + - name: dv_agencia_executor + description: "Dígito Verificador da Agência Bancária." + - name: nome_agencia_executor + description: "Nome da Agência Bancária do Executor." + - name: numero_conta_executor + description: "Número da Conta Bancária do Executor." + - name: dv_conta_executor + description: "Dígito Verificador da Conta Bancária." + - name: codigo_situacao_dado_bancario_executor + description: "Código da Situação da Conta Bancária" + - name: descricao_situacao_dado_bancario_executor + description: "Descrição textual da situação da conta bancária." + - name: dt_ingest + description: "Data e hora (UTC-3 Brasília) da ingestão dos dados para a camada bronze." + + data_tests: + - row_count_match: + source_table: transferegov_emendas.executor_especial + target_table: emendas.executor + - verificacao_tipagem: + nome_tabela: 'emendas.executor_especial' + nome_coluna: 'id_plano_acao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.executor_especial' + nome_coluna: 'id_executor' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.executor_especial' + nome_coluna: 'valor_custeio_executor' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.executor_especial' + nome_coluna: 'valor_investimento_executor' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.executor_especial' + nome_coluna: 'numero_agencia_executor' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.executor_especial' + nome_coluna: 'numero_conta_executor' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.executor_especial' + nome_coluna: 'codigo_situacao_dado_bancario_executor' + tipo_esperado: 'integer' + + - name: meta_especial + description: > + Tabela que detalha as metas físicas e financeiras estabelecidas por cada executor. + Contém o planejamento de execução, discriminando valores de emendas, recursos próprios, + rendimentos e doações, além da quantidade física e o tempo estimado (meses) para a meta. + meta: + tags: + - bronze + columns: + - name: id_executor + description: "Identificador Único do Executor do Planejamento." + - name: id_meta + description: "Identificação Única da Meta do Executor." + - name: sequencial_meta + description: "Sequencial numérico usado para ordenação das metas." + - name: nome_meta + description: "Nome ou título resumido da meta." + - name: desc_meta + description: "Descrição detalhada do que será realizado na meta." + - name: un_medida_meta + description: "Unidade de medida da meta." + - name: qt_uniade_meta + description: "Valor de custeio de Emenda Especial." + - name: valor_custeio_emenda_especial_meta + description: "Valor de Investimento de Emenda Especial." + - name: valor_investimento_emenda_especial_meta + description: "Valor de Custeio de Recursos Proprios." + - name: valor_custeio_recursos_proprios_meta + description: "Valor de Investimento de Recursos Proprios." + - name: valor_investimento_recursos_proprios_meta + description: "Valor de investimento aportado como recursos próprios." + - name: valor_custeio_rendimento_meta + description: "Valor de Custeio de Rendimentos." + - name: valor_investimento_rendimento_meta + description: "Valor de investimento originado de rendimentos." + - name: valor_custeio_doacao_meta + description: "Valor de custeio proveniente de doações." + - name: valor_investimento_doacao_meta + description: "Valor de investimento proveniente de doações." + - name: qt_meses_meta + description: "Quantidade de meses prevista para a execução da meta." + - name: dt_ingest + description: "Data e hora (UTC-3 Brasília) da ingestão dos dados para a camada bronze." + + data_tests: + - row_count_match: + source_table: transferegov_emendas.meta_especial + target_table: emendas.metas + - verificacao_tipagem: + nome_tabela: 'emendas.meta_especial' + nome_coluna: 'id_executor' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.meta_especial' + nome_coluna: 'id_meta' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.meta_especial' + nome_coluna: 'qt_uniade_meta' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.meta_especial' + nome_coluna: 'valor_custeio_emenda_especial_meta' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.meta_especial' + nome_coluna: 'valor_investimento_emenda_especial_meta' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.meta_especial' + nome_coluna: 'valor_custeio_recursos_proprios_meta' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.meta_especial' + nome_coluna: 'valor_investimento_recursos_proprios_meta' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.meta_especial' + nome_coluna: 'qt_meses_meta' + tipo_esperado: 'integer' + + - name: finalidade_especial + description: > + Tabela que detalha as finalidades das emendas especiais, vinculando cada executor às áreas + de políticas públicas beneficiadas. Permite identificar em quais setores (Saúde, Educação, etc.) + o recurso está sendo aplicado conforme a classificação do Transferegov. + meta: + tags: + - bronze + columns: + - name: id_executor + description: "Identificador Único do Executor do Planejamento (Chave de ligação com a tabela de executores)." + - name: cd_area_politica_publica_tipo_pt + description: "Código numérico que identifica o tipo da área de política pública." + - name: area_politica_publica_tipo_pt + description: "Nome descritivo do tipo da área de política pública." + - name: cd_area_politica_publica_pt + description: "Código da área específica de política pública (subdivisão do tipo)." + - name: area_politica_publica_pt + description: "Nome descritivo da área específica de política pública." + - name: dt_ingest + description: "Data e hora (UTC-3 Brasília) da ingestão dos dados para a camada bronze." + + data_tests: + - row_count_match: + source_table: transferegov_emendas.finalidade_especial + target_table: emendas.finalidades + - verificacao_tipagem: + nome_tabela: 'emendas.finalidade_especial' + nome_coluna: 'id_executor' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.finalidade_especial' + nome_coluna: 'cd_area_politica_publica_tipo_pt' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.finalidade_especial' + nome_coluna: 'cd_area_politica_publica_pt' + tipo_esperado: 'integer' diff --git a/airflow_lappis/dags/dbt/ipea/models/sources.yml b/airflow_lappis/dags/dbt/ipea/models/sources.yml index 162eca83..7da18cf3 100644 --- a/airflow_lappis/dags/dbt/ipea/models/sources.yml +++ b/airflow_lappis/dags/dbt/ipea/models/sources.yml @@ -53,6 +53,17 @@ sources: schema: transferegov_emendas tables: - name: programas_especiais + - name: planos_acao_especiais + - name: empenhos_especiais + - name: ordens_bancarias_especiais + - name: historico_pagamentos_especiais + - name: plano_trabalho_especial + - name: documentos_habeis_especiais + - name: relatorio_gestao_especial + - name: relatorios_gestao_novo_especial + - name: executor_especial + - name: metas_especiais + - name: finalidades_especiais - name: dados_abertos schema: camara_deputados From 290fd1dbcdb5871d9ed9dbd5bc3164fca59bd7d5 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Sat, 7 Mar 2026 18:28:14 -0300 Subject: [PATCH 211/317] feat: camada bronze senadores e docs --- airflow_lappis/dags/dbt/ipea/dbt_project.yml | 3 + .../models/senadores_dbt/bronze/schema.yml | 111 ++++++++++++++++++ .../models/senadores_dbt/bronze/senadores.sql | 20 ++++ .../dags/dbt/ipea/models/sources.yml | 5 + 4 files changed, 139 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/senadores_dbt/bronze/schema.yml create mode 100644 airflow_lappis/dags/dbt/ipea/models/senadores_dbt/bronze/senadores.sql diff --git a/airflow_lappis/dags/dbt/ipea/dbt_project.yml b/airflow_lappis/dags/dbt/ipea/dbt_project.yml index ecd1e813..e9dac400 100755 --- a/airflow_lappis/dags/dbt/ipea/dbt_project.yml +++ b/airflow_lappis/dags/dbt/ipea/dbt_project.yml @@ -56,6 +56,9 @@ models: deputados_dbt: +materialized: table +schema: deputados + senadores_dbt: + +materialized: table + +schema: senadores on-run-start: diff --git a/airflow_lappis/dags/dbt/ipea/models/senadores_dbt/bronze/schema.yml b/airflow_lappis/dags/dbt/ipea/models/senadores_dbt/bronze/schema.yml new file mode 100644 index 00000000..37c5352c --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/senadores_dbt/bronze/schema.yml @@ -0,0 +1,111 @@ +version: 2 + +models: + + # Senadores DBT + + ## Bronze + - name: senadores + description: > + Tabela com informações básicas sobre senadores federais obtidas por meio da API de Dados Abertos do Senado Federal. + Contém dados cadastrais e institucionais como identificador, nome parlamentar, sexo, forma de tratamento, + partido, unidade federativa, URLs de foto e página, e e-mail institucional. + Esta tabela representa a camada bronze do pipeline, aplicando padronização de tipos, + pequenas transformações e ajuste de fuso horário na data de ingestão. + Os dados são provenientes da fonte senado_federal.senadores (camada raw) e + passam por conversão explícita de tipos para garantir consistência e rastreabilidade. + meta: + tags: + - bronze + columns: + - name: id + description: > + Identificador único do senador na API do Senado Federal. + Utilizado como chave primária e referência para relacionamentos com outras tabelas. + + - name: nome_parlamentar + description: > + Nome parlamentar do senador conforme registrado no Senado Federal. + + - name: sexo + description: > + Sexo do senador (Masculino ou Feminino). + + - name: forma_tratamento + description: > + Forma de tratamento do senador (ex: Senador, Senadora). + + - name: url_foto + description: > + URL da foto oficial do senador disponibilizada pelo Senado Federal. + + - name: url_pagina + description: > + URL da página de perfil do senador no portal do Senado Federal. + + - name: email + description: > + Endereço de e-mail institucional do senador no Senado Federal. + + - name: sigla_partido + description: > + Sigla do partido ao qual o senador está filiado. + + - name: uf + description: > + Sigla da Unidade Federativa (UF) que o senador representa. + + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. + + tests: + - verificacao_tipagem: + nome_tabela: 'senadores.senadores' + nome_coluna: 'id' + tipo_esperado: 'integer' + + - verificacao_tipagem: + nome_tabela: 'senadores.senadores' + nome_coluna: 'nome_parlamentar' + tipo_esperado: 'text' + + - verificacao_tipagem: + nome_tabela: 'senadores.senadores' + nome_coluna: 'sexo' + tipo_esperado: 'text' + + - verificacao_tipagem: + nome_tabela: 'senadores.senadores' + nome_coluna: 'forma_tratamento' + tipo_esperado: 'text' + + - verificacao_tipagem: + nome_tabela: 'senadores.senadores' + nome_coluna: 'url_foto' + tipo_esperado: 'text' + + - verificacao_tipagem: + nome_tabela: 'senadores.senadores' + nome_coluna: 'url_pagina' + tipo_esperado: 'text' + + - verificacao_tipagem: + nome_tabela: 'senadores.senadores' + nome_coluna: 'email' + tipo_esperado: 'text' + + - verificacao_tipagem: + nome_tabela: 'senadores.senadores' + nome_coluna: 'sigla_partido' + tipo_esperado: 'text' + + - verificacao_tipagem: + nome_tabela: 'senadores.senadores' + nome_coluna: 'uf' + tipo_esperado: 'text' + + - verificacao_tipagem: + nome_tabela: 'senadores.senadores' + nome_coluna: 'dt_ingest' + tipo_esperado: 'timestamp with time zone' diff --git a/airflow_lappis/dags/dbt/ipea/models/senadores_dbt/bronze/senadores.sql b/airflow_lappis/dags/dbt/ipea/models/senadores_dbt/bronze/senadores.sql new file mode 100644 index 00000000..e6de70cc --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/senadores_dbt/bronze/senadores.sql @@ -0,0 +1,20 @@ +{{ config(materialized="table") }} + +with + senadores_raw as ( + select + id::integer as id, + nome_parlamentar::text as nome_parlamentar, + sexo::text as sexo, + forma_tratamento::text as forma_tratamento, + url_foto::text as url_foto, + url_pagina::text as url_pagina, + email::text as email, + sigla_partido::text as sigla_partido, + uf::text as uf, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("senado_federal", "senadores") }} + ) + +select * +from senadores_raw diff --git a/airflow_lappis/dags/dbt/ipea/models/sources.yml b/airflow_lappis/dags/dbt/ipea/models/sources.yml index 162eca83..8a04446e 100644 --- a/airflow_lappis/dags/dbt/ipea/models/sources.yml +++ b/airflow_lappis/dags/dbt/ipea/models/sources.yml @@ -58,3 +58,8 @@ sources: schema: camara_deputados tables: - name: deputados + + - name: senado_federal + schema: senado_federal + tables: + - name: senadores From 28072edc3ce01b358ec5f8768bb56eec2b974f1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateus=20Henrique=20Queiroz=20Magalh=C3=A3es=20Sousa?= <163928182+Mateushqms@users.noreply.github.com> Date: Fri, 13 Mar 2026 15:24:09 -0300 Subject: [PATCH 212/317] Chore/update airflow dag connections (#98) * feat: mudanca na funcao helper do postgres para fazer novas conexoes * chore: mudanca nas conexoes das dags do mir * refactor: mudanca de pastas do dbt do mir para a pasta do mir, remocao das pastas do mir que estavam na pasta do IPEA * refactor: ajustes do dbt_project do ipea, remove modelos que referenciam o mir * refactor: ajustes para passar no sonar --- .../dados_abertos/deputados_ingest_dag.py | 4 +- .../dados_abertos/senadores_ingest_dag.py | 4 +- .../documentos_habeis_especias_ingest_dag.py | 4 +- .../empenhos_especiais_ingest_dag.py | 9 ++- .../executor_especial_ingest_dag.py | 6 +- .../finalidade_especial_ingest_dag.py | 4 +- ...storico_pagamentos_especiais_ingest_dag.py | 4 +- .../metas_especiais_ingest_dag.py | 4 +- .../ordem_bancaria_especial_ingest_dag.py | 4 +- .../plano_trabalho_especial_ingest_dag.py | 4 +- .../planos_acao_especiais_ingest_dag.py | 4 +- .../programas_especiais_ingest_dag.py | 4 +- ...latorio_gestao_novo_especial_ingest_dag.py | 4 +- .../reltorio_gestao_ingest.py | 8 +-- airflow_lappis/dags/dbt/ipea/dbt_project.yml | 9 --- airflow_lappis/dags/dbt/mir/.user.yml | 1 + airflow_lappis/dags/dbt/mir/cosmos_dag.py | 30 +++++++++ airflow_lappis/dags/dbt/mir/dbt_project.yml | 38 +++++++++++ airflow_lappis/dags/dbt/mir/descriptions.yml | 0 .../dags/dbt/mir/macros/create_udfs.sql | 10 +++ .../macros/data_quality/row_count_match.sql | 14 ++++ .../data_quality/verificacao_tipagem.sql | 25 +++++++ .../dags/dbt/mir/macros/get_custom_schema.sql | 4 ++ .../mir/macros/metadata/generate_metadata.sql | 46 +++++++++++++ .../dbt/mir/macros/parse_financial_value.sql | 21 ++++++ airflow_lappis/dags/dbt/mir/macros/schema.yml | 24 +++++++ .../dags/dbt/mir/macros/udfs/f_format_nc.sql | 19 ++++++ .../dbt/mir/macros/udfs/f_parse_dates.sql | 44 ++++++++++++ .../models/deputados_dbt/bronze/deputados.sql | 0 .../models/deputados_dbt/bronze/schema.yaml | 0 .../emendas_dbt/bronze/documentos_habeis.sql | 0 .../emendas_dbt/bronze/empenhos_especiais.sql | 0 .../models/emendas_dbt/bronze/executor.sql | 0 .../models/emendas_dbt/bronze/finalidades.sql | 0 .../bronze/historico_pagamentos.sql | 0 .../models/emendas_dbt/bronze/metas.sql | 0 .../emendas_dbt/bronze/ordens_bancarias.sql | 0 .../emendas_dbt/bronze/planos_acoes.sql | 0 .../bronze/planos_trabalho_especial.sql | 0 .../models/emendas_dbt/bronze/programas.sql | 0 .../emendas_dbt/bronze/relatorio_gestao.sql | 0 .../bronze/relatorio_gestao_novo.sql | 0 .../models/emendas_dbt/bronze/schema.yml | 11 ++- .../mir/models/metadata/models_metadata.sql | 67 +++++++++++++++++++ .../dags/dbt/mir/models/metadata/schema.yml | 46 +++++++++++++ .../models/senadores_dbt/bronze/schema.yml | 0 .../models/senadores_dbt/bronze/senadores.sql | 0 .../dags/dbt/mir/models/sources.yml | 28 ++++++++ airflow_lappis/dags/dbt/mir/profiles.yml | 11 +++ .../dags/dbt/mir/seeds/estados_brasil.csv | 28 ++++++++ .../dbt/mir/snapshots/tables_snapshot.yml | 36 ++++++++++ airflow_lappis/helpers/postgres_helpers.py | 4 +- 52 files changed, 531 insertions(+), 52 deletions(-) create mode 100755 airflow_lappis/dags/dbt/mir/.user.yml create mode 100755 airflow_lappis/dags/dbt/mir/cosmos_dag.py create mode 100755 airflow_lappis/dags/dbt/mir/dbt_project.yml create mode 100644 airflow_lappis/dags/dbt/mir/descriptions.yml create mode 100644 airflow_lappis/dags/dbt/mir/macros/create_udfs.sql create mode 100644 airflow_lappis/dags/dbt/mir/macros/data_quality/row_count_match.sql create mode 100644 airflow_lappis/dags/dbt/mir/macros/data_quality/verificacao_tipagem.sql create mode 100755 airflow_lappis/dags/dbt/mir/macros/get_custom_schema.sql create mode 100644 airflow_lappis/dags/dbt/mir/macros/metadata/generate_metadata.sql create mode 100644 airflow_lappis/dags/dbt/mir/macros/parse_financial_value.sql create mode 100644 airflow_lappis/dags/dbt/mir/macros/schema.yml create mode 100644 airflow_lappis/dags/dbt/mir/macros/udfs/f_format_nc.sql create mode 100644 airflow_lappis/dags/dbt/mir/macros/udfs/f_parse_dates.sql rename airflow_lappis/dags/dbt/{ipea => mir}/models/deputados_dbt/bronze/deputados.sql (100%) rename airflow_lappis/dags/dbt/{ipea => mir}/models/deputados_dbt/bronze/schema.yaml (100%) rename airflow_lappis/dags/dbt/{ipea => mir}/models/emendas_dbt/bronze/documentos_habeis.sql (100%) rename airflow_lappis/dags/dbt/{ipea => mir}/models/emendas_dbt/bronze/empenhos_especiais.sql (100%) rename airflow_lappis/dags/dbt/{ipea => mir}/models/emendas_dbt/bronze/executor.sql (100%) rename airflow_lappis/dags/dbt/{ipea => mir}/models/emendas_dbt/bronze/finalidades.sql (100%) rename airflow_lappis/dags/dbt/{ipea => mir}/models/emendas_dbt/bronze/historico_pagamentos.sql (100%) rename airflow_lappis/dags/dbt/{ipea => mir}/models/emendas_dbt/bronze/metas.sql (100%) rename airflow_lappis/dags/dbt/{ipea => mir}/models/emendas_dbt/bronze/ordens_bancarias.sql (100%) rename airflow_lappis/dags/dbt/{ipea => mir}/models/emendas_dbt/bronze/planos_acoes.sql (100%) rename airflow_lappis/dags/dbt/{ipea => mir}/models/emendas_dbt/bronze/planos_trabalho_especial.sql (100%) rename airflow_lappis/dags/dbt/{ipea => mir}/models/emendas_dbt/bronze/programas.sql (100%) rename airflow_lappis/dags/dbt/{ipea => mir}/models/emendas_dbt/bronze/relatorio_gestao.sql (100%) rename airflow_lappis/dags/dbt/{ipea => mir}/models/emendas_dbt/bronze/relatorio_gestao_novo.sql (100%) rename airflow_lappis/dags/dbt/{ipea => mir}/models/emendas_dbt/bronze/schema.yml (99%) create mode 100644 airflow_lappis/dags/dbt/mir/models/metadata/models_metadata.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/metadata/schema.yml rename airflow_lappis/dags/dbt/{ipea => mir}/models/senadores_dbt/bronze/schema.yml (100%) rename airflow_lappis/dags/dbt/{ipea => mir}/models/senadores_dbt/bronze/senadores.sql (100%) create mode 100644 airflow_lappis/dags/dbt/mir/models/sources.yml create mode 100755 airflow_lappis/dags/dbt/mir/profiles.yml create mode 100644 airflow_lappis/dags/dbt/mir/seeds/estados_brasil.csv create mode 100644 airflow_lappis/dags/dbt/mir/snapshots/tables_snapshot.yml diff --git a/airflow_lappis/dags/data_ingest/dados_abertos/deputados_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_abertos/deputados_ingest_dag.py index ca13addc..9afe396c 100644 --- a/airflow_lappis/dags/data_ingest/dados_abertos/deputados_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_abertos/deputados_ingest_dag.py @@ -16,7 +16,7 @@ "retries": 1, "retry_delay": timedelta(minutes=5), }, - tags=["camara_deputados", "deputados", "dados_abertos"], + tags=["camara_deputados", "deputados", "dados_abertos", "MIR"], ) def deputados_ingest_dag() -> None: """DAG para buscar e armazenar dados de deputados da Câmara dos Deputados.""" @@ -26,7 +26,7 @@ def fetch_and_store_deputados() -> None: logging.info("[deputados_ingest_dag.py] Iniciando extração de deputados") api = ClienteDeputados() - postgres_conn_str = get_postgres_conn() + postgres_conn_str = get_postgres_conn("postgres_mir") db = ClientPostgresDB(postgres_conn_str) deputados_data = api.get_all_deputados() diff --git a/airflow_lappis/dags/data_ingest/dados_abertos/senadores_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_abertos/senadores_ingest_dag.py index d1645cb4..89804420 100644 --- a/airflow_lappis/dags/data_ingest/dados_abertos/senadores_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_abertos/senadores_ingest_dag.py @@ -16,7 +16,7 @@ "retries": 1, "retry_delay": timedelta(minutes=5), }, - tags=["senado_federal", "senadores", "dados_abertos"], + tags=["senado_federal", "senadores", "dados_abertos", "MIR"], ) def senadores_ingest_dag() -> None: """DAG para buscar e armazenar dados de senadores do Senado Federal.""" @@ -26,7 +26,7 @@ def fetch_and_store_senadores() -> None: logging.info("[senadores_ingest_dag.py] Iniciando extração de senadores") api = ClienteSenadores() - postgres_conn_str = get_postgres_conn() + postgres_conn_str = get_postgres_conn("postgres_mir") db = ClientPostgresDB(postgres_conn_str) senadores_data = api.get_senadores_atuais() diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/documentos_habeis_especias_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/documentos_habeis_especias_ingest_dag.py index 5d284fce..3acee287 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/documentos_habeis_especias_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/documentos_habeis_especias_ingest_dag.py @@ -16,7 +16,7 @@ "retries": 1, "retry_delay": timedelta(minutes=5), }, - tags=["transfere_gov_api", "documentos_especiais"], + tags=["transfere_gov_api", "documentos_especiais", "MIR"], ) def api_documentos_habeis_especiais_dag() -> None: """DAG para buscar e armazenar documentos hábeis especiais do Transfere Gov.""" @@ -29,7 +29,7 @@ def fetch_and_store_documentos_habeis_especiais() -> None: ) api = ClienteTransfereGov() - postgres_conn_str = get_postgres_conn() + postgres_conn_str = get_postgres_conn('postgres_mir') db = ClientPostgresDB(postgres_conn_str) # Busca todos os documentos hábeis especiais com paginação automática diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/empenhos_especiais_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/empenhos_especiais_ingest_dag.py index e7d40c79..69542a48 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/empenhos_especiais_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/empenhos_especiais_ingest_dag.py @@ -16,7 +16,7 @@ "retries": 1, "retry_delay": timedelta(minutes=5), }, - tags=["transfere_gov_api", "empenhos_especiais"], + tags=["transfere_gov_api", "empenhos_especiais", "MIR"], ) def api_empenhos_especiais_dag() -> None: """DAG para buscar e armazenar empenhos especiais do Transfere Gov.""" @@ -24,12 +24,11 @@ def api_empenhos_especiais_dag() -> None: @task def fetch_and_store_empenhos_especiais() -> None: logging.info( - "[empenhos_especiais_ingest_dag.py] Iniciando extração de " - "empenhos especiais" + "[empenhos_especiais_ingest_dag.py] Iniciando extração de empenhos especiais" ) api = ClienteTransfereGov() - postgres_conn_str = get_postgres_conn() + postgres_conn_str = get_postgres_conn('postgres_mir') db = ClientPostgresDB(postgres_conn_str) # Busca todos os documentos hábeis especiais com paginação automática @@ -59,7 +58,7 @@ def fetch_and_store_empenhos_especiais() -> None: ) else: logging.warning( - "[empenhos_especiais_ingest_dag.py] Nenhum empenho " "especial encontrado" + "[empenhos_especiais_ingest_dag.py] Nenhum empenho especial encontrado" ) fetch_and_store_empenhos_especiais() diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/executor_especial_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/executor_especial_ingest_dag.py index 149f1c8a..faa19c2c 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/executor_especial_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/executor_especial_ingest_dag.py @@ -27,7 +27,7 @@ def chunk_list(lst: list, size: int) -> Iterator[list]: "retries": 0, # "retry_delay": timedelta(minutes=5), }, - tags=["transfere_gov_api", "planos_acao_especiais"], + tags=["transfere_gov_api", "planos_acao_especiais", "MIR"], ) def api_executor_especial_dag() -> None: @@ -39,7 +39,7 @@ def fetch_planos_acao() -> list: """ logging.info("[executor_especial] Buscando planos de ação...") - db = ClientPostgresDB(get_postgres_conn()) + db = ClientPostgresDB(get_postgres_conn("postgres_mir")) query = """ SELECT DISTINCT id_plano_acao @@ -69,7 +69,7 @@ def process_chunk(chunk: list) -> None: - Insere no Postgres com UPSERT """ api = ClienteTransfereGov() - db = ClientPostgresDB(get_postgres_conn()) + db = ClientPostgresDB(get_postgres_conn("postgres_mir")) timestamp = datetime.now().isoformat() all_executores = [] diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/finalidade_especial_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/finalidade_especial_ingest_dag.py index 00b6760c..7e8222ca 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/finalidade_especial_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/finalidade_especial_ingest_dag.py @@ -16,7 +16,7 @@ "retries": 1, "retry_delay": timedelta(minutes=5), }, - tags=["transfere_gov_api", "finalidade_especial"], + tags=["transfere_gov_api", "finalidade_especial", "MIR"], ) def api_finalidade_especial_dag() -> None: """DAG para buscar e armazenar finalidades especiais do Transfere Gov.""" @@ -28,7 +28,7 @@ def fetch_and_store_finalidade_especial() -> None: ) api = ClienteTransfereGov() - postgres_conn_str = get_postgres_conn() + postgres_conn_str = get_postgres_conn('postgres_mir') db = ClientPostgresDB(postgres_conn_str) # Busca todas as finalidades especiais com paginação automática diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/historico_pagamentos_especiais_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/historico_pagamentos_especiais_ingest_dag.py index 574c2b7a..a81d696e 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/historico_pagamentos_especiais_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/historico_pagamentos_especiais_ingest_dag.py @@ -16,7 +16,7 @@ "retries": 1, "retry_delay": timedelta(minutes=5), }, - tags=["transfere_gov_api", "historico_pagamentos_especiais"], + tags=["transfere_gov_api", "historico_pagamentos_especiais", "MIR"], ) def api_historico_pagamentos_especiais_dag() -> None: """DAG para buscar e armazenar histórico de pagamentos especiais do Transfere Gov.""" @@ -29,7 +29,7 @@ def fetch_and_store_historico_pagamentos_especiais() -> None: ) api = ClienteTransfereGov() - postgres_conn_str = get_postgres_conn() + postgres_conn_str = get_postgres_conn('postgres_mir') db = ClientPostgresDB(postgres_conn_str) # Busca todos os documentos hábeis especiais com paginação automática diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/metas_especiais_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/metas_especiais_ingest_dag.py index 00018b02..07e84b08 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/metas_especiais_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/metas_especiais_ingest_dag.py @@ -16,7 +16,7 @@ "retries": 1, "retry_delay": timedelta(minutes=5), }, - tags=["transfere_gov_api", "metas_especiais"], + tags=["transfere_gov_api", "metas_especiais", "MIR"], ) def api_metas_especiais_dag() -> None: """DAG para buscar e armazenar metas especiais do Transfere Gov.""" @@ -28,7 +28,7 @@ def fetch_and_store_metas_especiais() -> None: ) api = ClienteTransfereGov() - postgres_conn_str = get_postgres_conn() + postgres_conn_str = get_postgres_conn('postgres_mir') db = ClientPostgresDB(postgres_conn_str) metas_data = api.get_all_metas_especiais() diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/ordem_bancaria_especial_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/ordem_bancaria_especial_ingest_dag.py index dde833a3..7bf0f884 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/ordem_bancaria_especial_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/ordem_bancaria_especial_ingest_dag.py @@ -16,7 +16,7 @@ "retries": 1, "retry_delay": timedelta(minutes=5), }, - tags=["transfere_gov_api", "ordem_bancaria_especial"], + tags=["transfere_gov_api", "ordem_bancaria_especial", "MIR"], ) def api_ordem_bancaria_especial_dag() -> None: """DAG para buscar e armazenar ordens bancárias especiais do Transfere Gov.""" @@ -28,7 +28,7 @@ def fetch_and_store_ordem_bancaria_especial() -> None: ) api = ClienteTransfereGov() - postgres_conn_str = get_postgres_conn() + postgres_conn_str = get_postgres_conn("postgres_mir") db = ClientPostgresDB(postgres_conn_str) # Busca todas as ordens bancárias especiais com paginação automática diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/plano_trabalho_especial_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/plano_trabalho_especial_ingest_dag.py index 620a917b..559d084d 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/plano_trabalho_especial_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/plano_trabalho_especial_ingest_dag.py @@ -16,7 +16,7 @@ "retries": 1, "retry_delay": timedelta(minutes=5), }, - tags=["transfere_gov_api", "plano_trabalho"], + tags=["transfere_gov_api", "plano_trabalho", "MIR"], ) def api_plano_trabalho_especial_dag() -> None: """DAG para buscar e armazenar planos de trabalho especiais do Transfere Gov.""" @@ -29,7 +29,7 @@ def fetch_and_store_plano_trabalho_especial() -> None: ) api = ClienteTransfereGov() - postgres_conn_str = get_postgres_conn() + postgres_conn_str = get_postgres_conn("postgres_mir") db = ClientPostgresDB(postgres_conn_str) plano_data = api.get_all_plano_trabalho_especial(page_size=1000) diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/planos_acao_especiais_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/planos_acao_especiais_ingest_dag.py index 150ccdd4..d6675ec9 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/planos_acao_especiais_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/planos_acao_especiais_ingest_dag.py @@ -16,7 +16,7 @@ "retries": 1, "retry_delay": timedelta(minutes=5), }, - tags=["transfere_gov_api", "planos_acao_especiais"], + tags=["transfere_gov_api", "planos_acao_especiais", "MIR"], ) def api_planos_acao_especiais_dag() -> None: """DAG para buscar e armazenar planos de ação especiais do Transfere Gov.""" @@ -29,7 +29,7 @@ def fetch_and_store_planos_acao_especiais() -> None: ) api = ClienteTransfereGov() - postgres_conn_str = get_postgres_conn() + postgres_conn_str = get_postgres_conn("postgres_mir") db = ClientPostgresDB(postgres_conn_str) # Buscar IDs dos programas especiais diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/programas_especiais_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/programas_especiais_ingest_dag.py index adda0a64..5aa38d37 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/programas_especiais_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/programas_especiais_ingest_dag.py @@ -16,7 +16,7 @@ "retries": 1, "retry_delay": timedelta(minutes=5), }, - tags=["transfere_gov_api", "programas_especiais"], + tags=["transfere_gov_api", "programas_especiais", "MIR"], ) def api_programas_especiais_dag() -> None: """DAG para buscar e armazenar programas especiais do Transfere Gov.""" @@ -28,7 +28,7 @@ def fetch_and_store_programas_especiais() -> None: ) api = ClienteTransfereGov() - postgres_conn_str = get_postgres_conn() + postgres_conn_str = get_postgres_conn("postgres_mir") db = ClientPostgresDB(postgres_conn_str) # Busca todos os programas especiais com paginação automática diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/relatorio_gestao_novo_especial_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/relatorio_gestao_novo_especial_ingest_dag.py index 918161a4..d026c908 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/relatorio_gestao_novo_especial_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/relatorio_gestao_novo_especial_ingest_dag.py @@ -16,7 +16,7 @@ "retries": 1, "retry_delay": timedelta(minutes=5), }, - tags=["transfere_gov_api", "relatorio_gestao_novo_especial"], + tags=["transfere_gov_api", "relatorio_gestao_novo_especial", "MIR"], ) def api_relatorio_gestao_novo_especial_dag() -> None: """DAG para buscar e armazenar relatórios de gestão novo especial do Transfere Gov""" @@ -29,7 +29,7 @@ def fetch_and_store_relatorios_gestao_novo_especial() -> None: ) api = ClienteTransfereGov() - postgres_conn_str = get_postgres_conn() + postgres_conn_str = get_postgres_conn("postgres_mir") db = ClientPostgresDB(postgres_conn_str) # Busca todos os relatórios de gestão novo especial com paginação automática diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/reltorio_gestao_ingest.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/reltorio_gestao_ingest.py index 90ebce80..9e7196f1 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/reltorio_gestao_ingest.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/reltorio_gestao_ingest.py @@ -65,7 +65,7 @@ def _remove_duplicates(all_relatorios: list) -> list: "retries": 0, # "retry_delay": timedelta(minutes=5), }, - tags=["transfere_gov_api", "relatorio_gestao_especial"], + tags=["transfere_gov_api", "relatorio_gestao_especial", "MIR"], ) def api_relatorio_gestao_especial_dag() -> None: @@ -77,7 +77,7 @@ def setup_table() -> None: """ logging.info("[relatorio_gestao] Configurando tabela...") - db = ClientPostgresDB(get_postgres_conn()) + db = ClientPostgresDB(get_postgres_conn("postgres_mir")) # Remove a tabela antiga se existir (para garantir estrutura correta) db.drop_table_if_exists( @@ -111,7 +111,7 @@ def fetch_planos_acao() -> list: """ logging.info("[relatorio_gestao] Buscando planos de ação...") - db = ClientPostgresDB(get_postgres_conn()) + db = ClientPostgresDB(get_postgres_conn("postgres_mir")) # Mantive a mesma query da DAG anterior, pois o filtro é por plano de ação query = """ @@ -141,7 +141,7 @@ def process_chunk(chunk: list) -> None: - Insere no Postgres com UPSERT """ api = ClienteTransfereGov() - db = ClientPostgresDB(get_postgres_conn()) + db = ClientPostgresDB(get_postgres_conn("postgres_mir")) timestamp = datetime.now().isoformat() all_relatorios = [] diff --git a/airflow_lappis/dags/dbt/ipea/dbt_project.yml b/airflow_lappis/dags/dbt/ipea/dbt_project.yml index e9dac400..17337daa 100755 --- a/airflow_lappis/dags/dbt/ipea/dbt_project.yml +++ b/airflow_lappis/dags/dbt/ipea/dbt_project.yml @@ -45,20 +45,11 @@ models: +schema: orcamento views: +materialized: view - emendas_dbt: - +materialized: table - +schema: emendas # siape_dbt: # +materialized: table # +schema: siape # views: # +materialized: view - deputados_dbt: - +materialized: table - +schema: deputados - senadores_dbt: - +materialized: table - +schema: senadores on-run-start: diff --git a/airflow_lappis/dags/dbt/mir/.user.yml b/airflow_lappis/dags/dbt/mir/.user.yml new file mode 100755 index 00000000..43198208 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/.user.yml @@ -0,0 +1 @@ +id: d025f823-c5b2-49c6-b826-226fb25f1ad8 diff --git a/airflow_lappis/dags/dbt/mir/cosmos_dag.py b/airflow_lappis/dags/dbt/mir/cosmos_dag.py new file mode 100755 index 00000000..e139edeb --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/cosmos_dag.py @@ -0,0 +1,30 @@ +import os +from datetime import datetime +from cosmos import DbtDag, ProjectConfig, ProfileConfig, ExecutionConfig +from cosmos.constants import DBT_LOG_PATH_ENVVAR + +dbt_log_path = "/tmp/dbt_logs" # NOSONAR +os.makedirs(dbt_log_path, exist_ok=True) +os.environ[DBT_LOG_PATH_ENVVAR] = dbt_log_path + +profile_config = ProfileConfig( + profiles_yml_filepath=f"{os.environ['AIRFLOW_REPO_BASE']}/dags/dbt/mir/profiles.yml", + profile_name="mir", + target_name="prod", +) + +my_cosmos_dag = DbtDag( + project_config=ProjectConfig(f"{os.environ['AIRFLOW_REPO_BASE']}/dags/dbt/mir"), + profile_config=profile_config, + execution_config=ExecutionConfig( + dbt_executable_path=f"{os.environ['AIRFLOW_REPO_BASE']}/.local/bin/dbt", + ), + # Expressãp cron para agendar a execução do DAG diariamente às 01:00 + # Futuralmente isso pode ser substituído por um cronograma mais específico + # com dependências entre os DAGs + schedule_interval=" 0 1 * * *", + start_date=datetime(2025, 1, 1), + catchup=False, + dag_id="mir_cosmos_dag", + default_args={"retries": 2}, +) diff --git a/airflow_lappis/dags/dbt/mir/dbt_project.yml b/airflow_lappis/dags/dbt/mir/dbt_project.yml new file mode 100755 index 00000000..b4fc19e5 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/dbt_project.yml @@ -0,0 +1,38 @@ +name: 'mir' + +version: 1.0.0 +config-version: 2 + +profile: mir + +model-paths: ["models"] +analysis-paths: ["analyses"] +test-paths: ["tests"] +seed-paths: ["seeds"] +macro-paths: ["macros"] +snapshot-paths: ["snapshots"] + +clean-targets: + - "target" + - "dbt_packages" + - "logs" + +models: + mir: + +database: analytics + metadata: + +materialized: incremental + +schema: metadata + emendas_dbt: + +materialized: table + +schema: emendas + deputados_dbt: + +materialized: table + +schema: deputados + senadores_dbt: + +materialized: table + +schema: senadores + + +on-run-start: + - '{{create_udfs()}}' \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/descriptions.yml b/airflow_lappis/dags/dbt/mir/descriptions.yml new file mode 100644 index 00000000..e69de29b diff --git a/airflow_lappis/dags/dbt/mir/macros/create_udfs.sql b/airflow_lappis/dags/dbt/mir/macros/create_udfs.sql new file mode 100644 index 00000000..dd230cf5 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/macros/create_udfs.sql @@ -0,0 +1,10 @@ +{% macro create_udfs() %} + +create schema if not exists {{ target.schema }}; + + {{ create_f_parse_dates() }} + ; + {{ create_f_format_nc() }} + ; + +{% endmacro %} diff --git a/airflow_lappis/dags/dbt/mir/macros/data_quality/row_count_match.sql b/airflow_lappis/dags/dbt/mir/macros/data_quality/row_count_match.sql new file mode 100644 index 00000000..f248e30c --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/macros/data_quality/row_count_match.sql @@ -0,0 +1,14 @@ +{% macro test_row_count_match(model, source_table, target_table) %} + with + source_count as (select count(*) as row_count from {{ source_table }}), + target_count as (select count(*) as row_count from {{ target_table }}), + comparison as ( + select + source_count.row_count as source_row_count, + target_count.row_count as target_row_count + from source_count, target_count + ) + select * + from comparison + where source_row_count != target_row_count +{% endmacro %} diff --git a/airflow_lappis/dags/dbt/mir/macros/data_quality/verificacao_tipagem.sql b/airflow_lappis/dags/dbt/mir/macros/data_quality/verificacao_tipagem.sql new file mode 100644 index 00000000..34c3d392 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/macros/data_quality/verificacao_tipagem.sql @@ -0,0 +1,25 @@ +{% macro test_verificacao_tipagem(model, nome_tabela, nome_coluna, tipo_esperado) %} + with + column_info as ( + select + table_schema, + table_name, -- Nome real da coluna no information_schema + column_name, -- Nome real da coluna no information_schema + data_type + from information_schema.columns + where + table_schema || '.' || table_name = '{{ nome_tabela }}' + and column_name = '{{ nome_coluna }}' + ), + comparison as ( + select + '{{ nome_tabela }}' as nome_tabela, + '{{ nome_coluna }}' as nome_coluna, + '{{ tipo_esperado }}' as tipo_esperado, + data_type as actual_type + from column_info + ) + select * + from comparison + where actual_type != tipo_esperado +{% endmacro %} diff --git a/airflow_lappis/dags/dbt/mir/macros/get_custom_schema.sql b/airflow_lappis/dags/dbt/mir/macros/get_custom_schema.sql new file mode 100755 index 00000000..79444e9a --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/macros/get_custom_schema.sql @@ -0,0 +1,4 @@ +-- built-in schema generator +{% macro generate_schema_name(custom_schema_name, node) -%} + {{ generate_schema_name_for_env(custom_schema_name, node) }} +{%- endmacro %} diff --git a/airflow_lappis/dags/dbt/mir/macros/metadata/generate_metadata.sql b/airflow_lappis/dags/dbt/mir/macros/metadata/generate_metadata.sql new file mode 100644 index 00000000..8bfb115b --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/macros/metadata/generate_metadata.sql @@ -0,0 +1,46 @@ +{% macro get_model_metadata() %} +{# + Esta macro retorna os metadados do modelo atual. + Pode ser usada em post-hooks para registrar metadados automaticamente. +#} + SELECT + '{{ this.schema }}' AS schema_name, + '{{ this.name }}' AS table_name, + '{{ this.database }}' AS database_name, + ('{{ run_started_at }}'::TIMESTAMP AT TIME ZONE 'UTC' AT TIME ZONE 'America/Sao_Paulo') AS dt_transform, + '{{ invocation_id }}' AS run_id +{% endmacro %} + + +{% macro register_model_metadata() %} +{# + Esta macro registra os metadados do modelo em uma tabela central. + Deve ser usada como post-hook nos modelos que deseja rastrear. + + Uso no dbt_project.yml: + models: + ipea: + +post-hook: + - "{{ register_model_metadata() }}" +#} + + INSERT INTO {{ target.database }}.metadata.models_metadata ( + schema_name, + table_name, + database_name, + dt_transform, + run_id + ) + VALUES ( + '{{ this.schema }}', + '{{ this.name }}', + '{{ this.database }}', + ('{{ run_started_at }}'::TIMESTAMP AT TIME ZONE 'UTC' AT TIME ZONE 'America/Sao_Paulo'), + '{{ invocation_id }}' + ) + ON CONFLICT (schema_name, table_name) + DO UPDATE SET + dt_transform = EXCLUDED.dt_transform, + run_id = EXCLUDED.run_id; + +{% endmacro %} diff --git a/airflow_lappis/dags/dbt/mir/macros/parse_financial_value.sql b/airflow_lappis/dags/dbt/mir/macros/parse_financial_value.sql new file mode 100644 index 00000000..437b673c --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/macros/parse_financial_value.sql @@ -0,0 +1,21 @@ +{% macro parse_financial_value(column_name) %} + + case + when {{ column_name }} is null or trim({{ column_name }}) = '' + then 0.00::numeric(15, 2) + when {{ column_name }} like '%NaN%' + then 0.00::numeric(15, 2) + when {{ column_name }} like '(%' + then + regexp_replace( + replace(coalesce({{ column_name }}, '0'), '.', ''), + '(\()?(\d+),(\d+)(\))?', + '-\2.\3' + )::numeric(15, 2) + else + replace( + replace(coalesce({{ column_name }}, '0'), '.', ''), ',', '.' + )::numeric(15, 2) + end + +{% endmacro %} diff --git a/airflow_lappis/dags/dbt/mir/macros/schema.yml b/airflow_lappis/dags/dbt/mir/macros/schema.yml new file mode 100644 index 00000000..694f3b23 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/macros/schema.yml @@ -0,0 +1,24 @@ + +version: 2 + +macros: + - name: create_udfs + description: > + Função que cria as UDFs necessárias para o funcionamento do projeto. + Essa função deve ser chamada no início de cada run para garantir que todas as UDFs estejam disponíveis. + + - name: generate_schema_name + description: > + Função que gera o nome do schema a ser utilizado no projeto. + A função dentro desta macro é built-in do dbt. + + ## UDFS + - name: create_f_parse_dates + description: > + Função que cria a UDF f_parse_dates, que é utilizada para converter texto no formato MÊS(texto)/ANO(numero) em datas. + arguments: + - name: in_text + type: text + description: > + Texto a ser convertido em data. + O texto deve estar no formato MÊS(texto)/ANO(numero). Ex.: FEV/2024 -> 2024-02-01 \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/macros/udfs/f_format_nc.sql b/airflow_lappis/dags/dbt/mir/macros/udfs/f_format_nc.sql new file mode 100644 index 00000000..f7a06c86 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/macros/udfs/f_format_nc.sql @@ -0,0 +1,19 @@ +{% macro create_f_format_nc() %} + create or replace function {{ target.schema }}.format_nc(in_text text) + returns text + as $$ + + with + + pre_process as ( + select left(in_text, 7) as prefix, + right(in_text, 4)::numeric as posfix + ) + + select concat(prefix, to_char(posfix, 'FM00000')) as result + from pre_process + + $$ + language sql + ; +{% endmacro %} diff --git a/airflow_lappis/dags/dbt/mir/macros/udfs/f_parse_dates.sql b/airflow_lappis/dags/dbt/mir/macros/udfs/f_parse_dates.sql new file mode 100644 index 00000000..3fd8693e --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/macros/udfs/f_parse_dates.sql @@ -0,0 +1,44 @@ +-- Essa fun +{% macro create_f_parse_dates() %} + + create or replace function {{ target.schema }}.parse_date(in_text text) + returns date + as + $$ + + with + + split_column as ( + select + split_part(in_text, '/', 1) as mes, + split_part(in_text, '/', 2) as ano + ), + + fixed_month as ( + select + ano, + case + when mes = 'JAN' then '01' + when mes = 'FEV' then '02' + when mes = 'MAR' then '03' + when mes = 'ABR' then '04' + when mes = 'MAI' then '05' + when mes = 'JUN' then '06' + when mes = 'JUL' then '07' + when mes = 'AGO' then '08' + when mes = 'SET' then '09' + when mes = 'OUT' then '10' + when mes = 'NOV' then '11' + when mes = 'DEZ' then '12' + else mes end as mes_num + from split_column + ) + + select + (to_date(ano::numeric - 1 || '-' || '12', 'YYYY-MM') + (mes_num || ' months')::interval)::date as result + from fixed_month + $$ + language sql + ; + +{% endmacro %} diff --git a/airflow_lappis/dags/dbt/ipea/models/deputados_dbt/bronze/deputados.sql b/airflow_lappis/dags/dbt/mir/models/deputados_dbt/bronze/deputados.sql similarity index 100% rename from airflow_lappis/dags/dbt/ipea/models/deputados_dbt/bronze/deputados.sql rename to airflow_lappis/dags/dbt/mir/models/deputados_dbt/bronze/deputados.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/deputados_dbt/bronze/schema.yaml b/airflow_lappis/dags/dbt/mir/models/deputados_dbt/bronze/schema.yaml similarity index 100% rename from airflow_lappis/dags/dbt/ipea/models/deputados_dbt/bronze/schema.yaml rename to airflow_lappis/dags/dbt/mir/models/deputados_dbt/bronze/schema.yaml diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/documentos_habeis.sql b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/documentos_habeis.sql similarity index 100% rename from airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/documentos_habeis.sql rename to airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/documentos_habeis.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/empenhos_especiais.sql b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/empenhos_especiais.sql similarity index 100% rename from airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/empenhos_especiais.sql rename to airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/empenhos_especiais.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/executor.sql b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/executor.sql similarity index 100% rename from airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/executor.sql rename to airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/executor.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/finalidades.sql b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/finalidades.sql similarity index 100% rename from airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/finalidades.sql rename to airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/finalidades.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/historico_pagamentos.sql b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/historico_pagamentos.sql similarity index 100% rename from airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/historico_pagamentos.sql rename to airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/historico_pagamentos.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/metas.sql b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/metas.sql similarity index 100% rename from airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/metas.sql rename to airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/metas.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/ordens_bancarias.sql b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/ordens_bancarias.sql similarity index 100% rename from airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/ordens_bancarias.sql rename to airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/ordens_bancarias.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/planos_acoes.sql b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/planos_acoes.sql similarity index 100% rename from airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/planos_acoes.sql rename to airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/planos_acoes.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/planos_trabalho_especial.sql b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/planos_trabalho_especial.sql similarity index 100% rename from airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/planos_trabalho_especial.sql rename to airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/planos_trabalho_especial.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/programas.sql b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/programas.sql similarity index 100% rename from airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/programas.sql rename to airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/programas.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/relatorio_gestao.sql b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/relatorio_gestao.sql similarity index 100% rename from airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/relatorio_gestao.sql rename to airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/relatorio_gestao.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/relatorio_gestao_novo.sql b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/relatorio_gestao_novo.sql similarity index 100% rename from airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/relatorio_gestao_novo.sql rename to airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/relatorio_gestao_novo.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/schema.yml b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/schema.yml similarity index 99% rename from airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/schema.yml rename to airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/schema.yml index 3decb28a..a020153a 100644 --- a/airflow_lappis/dags/dbt/ipea/models/emendas_dbt/bronze/schema.yml +++ b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/schema.yml @@ -275,7 +275,7 @@ models: nome_coluna: 'id_programa' tipo_esperado: 'integer' - - name: empenhos_emendas + - name: empenhos_especiais description: > Tabela com informações sobre empenhos (Notas de Empenho) relacionados a emendas parlamentares do TransfereGov. Contém dados sobre a execução orçamentária dos empenhos, incluindo valores, beneficiários, @@ -372,9 +372,6 @@ models: description: > Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. tests: - - row_count_match: - source_table: transferegov_emendas.empenhos_especiais - target_table: emendas.empenhos_emendas - verificacao_tipagem: nome_tabela: 'emendas.empenhos_emendas' nome_coluna: 'id_empenho' @@ -844,7 +841,7 @@ models: nome_coluna: 'id_plano_acao' tipo_esperado: 'integer' - - name: executor_especial + - name: executor description: > Tabela com informações detalhadas sobre os executores dos planos de ação das emendas. Contém dados cadastrais do executor, detalhes do objeto da execução, valores financeiros de custeio @@ -924,7 +921,7 @@ models: nome_coluna: 'codigo_situacao_dado_bancario_executor' tipo_esperado: 'integer' - - name: meta_especial + - name: metas description: > Tabela que detalha as metas físicas e financeiras estabelecidas por cada executor. Contém o planejamento de execução, discriminando valores de emendas, recursos próprios, @@ -1005,7 +1002,7 @@ models: nome_coluna: 'qt_meses_meta' tipo_esperado: 'integer' - - name: finalidade_especial + - name: finalidades description: > Tabela que detalha as finalidades das emendas especiais, vinculando cada executor às áreas de políticas públicas beneficiadas. Permite identificar em quais setores (Saúde, Educação, etc.) diff --git a/airflow_lappis/dags/dbt/mir/models/metadata/models_metadata.sql b/airflow_lappis/dags/dbt/mir/models/metadata/models_metadata.sql new file mode 100644 index 00000000..f1981994 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/metadata/models_metadata.sql @@ -0,0 +1,67 @@ +{{ + config( + materialized='incremental', + unique_key=['schema_name', 'table_name'], + on_schema_change='sync_all_columns' + ) +}} + +{# + Tabela de Metadados dos Modelos dbt + =================================== + + Esta tabela armazena metadados de todos os modelos executados no dbt. + + Campos principais: + - schema_name: Schema do modelo + - table_name: Nome da tabela/modelo + - dt_transform: Data da última transformação (quando o modelo foi executado) + - run_id: ID único da execução do dbt + + A tabela é atualizada de forma incremental, mantendo apenas o registro + mais recente para cada combinação de schema + table_name. +#} + +WITH dbt_models AS ( + {# + Usando a função graph do dbt para iterar sobre todos os modelos do projeto. + Isso garante que capturamos metadados de todos os modelos definidos. + #} + {% set models_data = [] %} + + {% for node in graph.nodes.values() %} + {% if node.resource_type == 'model' %} + {% do models_data.append({ + 'schema_name': node.schema, + 'table_name': node.name, + 'database_name': node.database, + 'materialization': node.config.materialized, + 'description': node.description | default('') | replace("'", "''") + }) %} + {% endif %} + {% endfor %} + + {% for model in models_data %} + SELECT + '{{ model.schema_name }}' AS schema_name, + '{{ model.table_name }}' AS table_name, + '{{ model.database_name }}' AS database_name, + '{{ model.materialization }}' AS materialization, + '{{ model.description[:500] }}' AS description, + ('{{ run_started_at }}'::TIMESTAMP AT TIME ZONE 'UTC' AT TIME ZONE 'America/Sao_Paulo') AS dt_transform, + '{{ invocation_id }}' AS run_id + {% if not loop.last %} + UNION ALL + {% endif %} + {% endfor %} +) + +SELECT + schema_name, + table_name, + database_name, + materialization, + description, + dt_transform, + run_id +FROM dbt_models diff --git a/airflow_lappis/dags/dbt/mir/models/metadata/schema.yml b/airflow_lappis/dags/dbt/mir/models/metadata/schema.yml new file mode 100644 index 00000000..e4fce0ab --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/metadata/schema.yml @@ -0,0 +1,46 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/dbt-labs/dbt-jsonschema/main/schemas/latest/dbt_yml_files-latest.json + +version: 2 + +models: + - name: models_metadata + description: > + Tabela central de metadados que armazena informações sobre todos os modelos dbt executados. + Cada linha representa um modelo único, identificado pela combinação de schema e table_name. + A tabela é atualizada de forma incremental, mantendo histórico das execuções. + meta: + tags: + - metadata + - governance + columns: + - name: schema_name + description: Nome do schema onde o modelo está localizado. + tests: + - not_null + + - name: table_name + description: Nome da tabela/modelo. + tests: + - not_null + + - name: database_name + description: Nome do banco de dados onde o modelo está materializado. + + - name: materialization + description: Tipo de materialização do modelo (table, view, incremental, etc). + + - name: description + description: Descrição do modelo extraída do schema.yml. + + - name: dt_transform + description: > + Data e hora em que o modelo foi transformado/executado pela última vez. + Corresponde ao momento em que a execução do dbt foi iniciada (run_started_at). + Timezone: America/Sao_Paulo (UTC-3). + tests: + - not_null + + - name: run_id + description: > + Identificador único da execução do dbt (invocation_id). + Permite rastrear qual execução gerou a transformação. diff --git a/airflow_lappis/dags/dbt/ipea/models/senadores_dbt/bronze/schema.yml b/airflow_lappis/dags/dbt/mir/models/senadores_dbt/bronze/schema.yml similarity index 100% rename from airflow_lappis/dags/dbt/ipea/models/senadores_dbt/bronze/schema.yml rename to airflow_lappis/dags/dbt/mir/models/senadores_dbt/bronze/schema.yml diff --git a/airflow_lappis/dags/dbt/ipea/models/senadores_dbt/bronze/senadores.sql b/airflow_lappis/dags/dbt/mir/models/senadores_dbt/bronze/senadores.sql similarity index 100% rename from airflow_lappis/dags/dbt/ipea/models/senadores_dbt/bronze/senadores.sql rename to airflow_lappis/dags/dbt/mir/models/senadores_dbt/bronze/senadores.sql diff --git a/airflow_lappis/dags/dbt/mir/models/sources.yml b/airflow_lappis/dags/dbt/mir/models/sources.yml new file mode 100644 index 00000000..71e1d4fc --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/sources.yml @@ -0,0 +1,28 @@ +version: 2 + +sources: + - name: transferegov_emendas + schema: transferegov_emendas + tables: + - name: programas_especiais + - name: planos_acao_especiais + - name: empenhos_especiais + - name: ordens_bancarias_especiais + - name: historico_pagamentos_especiais + - name: plano_trabalho_especial + - name: documentos_habeis_especiais + - name: relatorio_gestao_especial + - name: relatorios_gestao_novo_especial + - name: executor_especial + - name: metas_especiais + - name: finalidades_especiais + + - name: dados_abertos + schema: camara_deputados + tables: + - name: deputados + + - name: senado_federal + schema: senado_federal + tables: + - name: senadores diff --git a/airflow_lappis/dags/dbt/mir/profiles.yml b/airflow_lappis/dags/dbt/mir/profiles.yml new file mode 100755 index 00000000..c5ef73cc --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/profiles.yml @@ -0,0 +1,11 @@ +mir: + target: prod + outputs: + prod: + type: postgres + host: "{{ env_var('DB_DW_HOST_MIR', 'postgres') }}" + user: "{{ env_var('DB_DW_USER_MIR', 'postgres_dw') }}" + password: "{{ env_var('DB_DW_PASSWORD_MIR', 'postgres_dw') }}" + port: "{{ env_var('DB_DW_PORT_MIR', '5432') | int }}" + dbname: "{{ env_var('DB_DW_DBNAME_MIR', 'data_warehouse') }}" + schema: "{{ env_var('DB_DW_SCHEMA_MIR', 'mir') }}" \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/seeds/estados_brasil.csv b/airflow_lappis/dags/dbt/mir/seeds/estados_brasil.csv new file mode 100644 index 00000000..eaa791fd --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/seeds/estados_brasil.csv @@ -0,0 +1,28 @@ +sigla_uf,nome_uf +AC,ACRE +AL,ALAGOAS +AP,AMAPÁ +AM,AMAZONAS +BA,BAHIA +CE,CEARÁ +DF,DISTRITO FEDERAL +ES,ESPÍRITO SANTO +GO,GOIÁS +MA,MARANHÃO +MT,MATO GROSSO +MS,MATO GROSSO DO SUL +MG,MINAS GERAIS +PA,PARÁ +PB,PARAÍBA +PR,PARANÁ +PE,PERNAMBUCO +PI,PIAUÍ +RJ,RIO DE JANEIRO +RN,RIO GRANDE DO NORTE +RS,RIO GRANDE DO SUL +RO,RONDÔNIA +RR,RORAIMA +SC,SANTA CATARINA +SP,SÃO PAULO +SE,SERGIPE +TO,TOCANTINS diff --git a/airflow_lappis/dags/dbt/mir/snapshots/tables_snapshot.yml b/airflow_lappis/dags/dbt/mir/snapshots/tables_snapshot.yml new file mode 100644 index 00000000..84d36234 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/snapshots/tables_snapshot.yml @@ -0,0 +1,36 @@ +snapshots: + - name: contratos_snapshot + relation: ref('contratos') + config: + schema: snapshots + database: analytics + unique_key: id + strategy: check + check_cols: [situacao, num_parcelas, valor_parcela, valor_global, valor_acumulado] + + - name: faturas_snapshot + relation: ref('faturas') + config: + schema: snapshots + database: analytics + unique_key: [id, id_empenho] + strategy: check + check_cols: [situacao, valor, juros, multa, glosa] + + - name: empenhos_snapshot + relation: ref('empenhos') + config: + schema: snapshots + database: analytics + unique_key: [id, contrato_id] + strategy: check + check_cols: [empenhado, aliquidar, liquidado, pago, rpinscrito, rpaliquidar, rpliquidado, rppago] + + - name: cronogramas_snapshot + relation: ref('cronogramas') + config: + schema: snapshots + database: analytics + unique_key: id + strategy: check + check_cols: [valor, retroativo, observacao] \ No newline at end of file diff --git a/airflow_lappis/helpers/postgres_helpers.py b/airflow_lappis/helpers/postgres_helpers.py index 50b287eb..4e0a55d4 100755 --- a/airflow_lappis/helpers/postgres_helpers.py +++ b/airflow_lappis/helpers/postgres_helpers.py @@ -2,9 +2,9 @@ from airflow.providers.postgres.hooks.postgres import PostgresHook -def get_postgres_conn() -> str: +def get_postgres_conn(data_base_name: str = "postgres_default") -> str: try: - hook = PostgresHook(postgres_conn_id="postgres_default") + hook = PostgresHook(postgres_conn_id=data_base_name) conn = hook.get_conn() schema = conn.info.dbname logging.info( From 4021d4c29ef3a60fce3d55a94e560e0e7d541344 Mon Sep 17 00:00:00 2001 From: Tiago Bittencourt Date: Fri, 13 Mar 2026 21:08:15 -0300 Subject: [PATCH 213/317] fix: refs pastas mir inesistentes (#100) --- airflow_lappis/dags/dbt/mir/dbt_project.yml | 2 -- .../dags/dbt/mir/seeds/estados_brasil.csv | 28 --------------- .../dbt/mir/snapshots/tables_snapshot.yml | 36 ------------------- 3 files changed, 66 deletions(-) delete mode 100644 airflow_lappis/dags/dbt/mir/seeds/estados_brasil.csv delete mode 100644 airflow_lappis/dags/dbt/mir/snapshots/tables_snapshot.yml diff --git a/airflow_lappis/dags/dbt/mir/dbt_project.yml b/airflow_lappis/dags/dbt/mir/dbt_project.yml index b4fc19e5..01211c0c 100755 --- a/airflow_lappis/dags/dbt/mir/dbt_project.yml +++ b/airflow_lappis/dags/dbt/mir/dbt_project.yml @@ -8,9 +8,7 @@ profile: mir model-paths: ["models"] analysis-paths: ["analyses"] test-paths: ["tests"] -seed-paths: ["seeds"] macro-paths: ["macros"] -snapshot-paths: ["snapshots"] clean-targets: - "target" diff --git a/airflow_lappis/dags/dbt/mir/seeds/estados_brasil.csv b/airflow_lappis/dags/dbt/mir/seeds/estados_brasil.csv deleted file mode 100644 index eaa791fd..00000000 --- a/airflow_lappis/dags/dbt/mir/seeds/estados_brasil.csv +++ /dev/null @@ -1,28 +0,0 @@ -sigla_uf,nome_uf -AC,ACRE -AL,ALAGOAS -AP,AMAPÁ -AM,AMAZONAS -BA,BAHIA -CE,CEARÁ -DF,DISTRITO FEDERAL -ES,ESPÍRITO SANTO -GO,GOIÁS -MA,MARANHÃO -MT,MATO GROSSO -MS,MATO GROSSO DO SUL -MG,MINAS GERAIS -PA,PARÁ -PB,PARAÍBA -PR,PARANÁ -PE,PERNAMBUCO -PI,PIAUÍ -RJ,RIO DE JANEIRO -RN,RIO GRANDE DO NORTE -RS,RIO GRANDE DO SUL -RO,RONDÔNIA -RR,RORAIMA -SC,SANTA CATARINA -SP,SÃO PAULO -SE,SERGIPE -TO,TOCANTINS diff --git a/airflow_lappis/dags/dbt/mir/snapshots/tables_snapshot.yml b/airflow_lappis/dags/dbt/mir/snapshots/tables_snapshot.yml deleted file mode 100644 index 84d36234..00000000 --- a/airflow_lappis/dags/dbt/mir/snapshots/tables_snapshot.yml +++ /dev/null @@ -1,36 +0,0 @@ -snapshots: - - name: contratos_snapshot - relation: ref('contratos') - config: - schema: snapshots - database: analytics - unique_key: id - strategy: check - check_cols: [situacao, num_parcelas, valor_parcela, valor_global, valor_acumulado] - - - name: faturas_snapshot - relation: ref('faturas') - config: - schema: snapshots - database: analytics - unique_key: [id, id_empenho] - strategy: check - check_cols: [situacao, valor, juros, multa, glosa] - - - name: empenhos_snapshot - relation: ref('empenhos') - config: - schema: snapshots - database: analytics - unique_key: [id, contrato_id] - strategy: check - check_cols: [empenhado, aliquidar, liquidado, pago, rpinscrito, rpaliquidar, rpliquidado, rppago] - - - name: cronogramas_snapshot - relation: ref('cronogramas') - config: - schema: snapshots - database: analytics - unique_key: id - strategy: check - check_cols: [valor, retroativo, observacao] \ No newline at end of file From e62fb62b8075655e35f4307d36cf9d074cda4da5 Mon Sep 17 00:00:00 2001 From: Mateushqms Date: Mon, 16 Mar 2026 14:00:37 -0300 Subject: [PATCH 214/317] =?UTF-8?q?feat:=20atualizar=20cliente=20TED=20e?= =?UTF-8?q?=20pipelines=20MIR=20no=20ingest=C3=A3o=20de=20dados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mir/notas_de_credito_ingest_mir_dag.py | 52 +++++++++++++++++ .../mir/plano_acao_ingest_mir_dag.py | 57 +++++++++++++++++++ .../mir/programa_financeira_ingest_mir_dag.py | 56 ++++++++++++++++++ .../mir/programas_ingest_mir_dag.py | 55 ++++++++++++++++++ airflow_lappis/plugins/cliente_ted.py | 49 ++++++++++++++++ 5 files changed, 269 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/transfere_gov/mir/notas_de_credito_ingest_mir_dag.py create mode 100644 airflow_lappis/dags/data_ingest/transfere_gov/mir/plano_acao_ingest_mir_dag.py create mode 100644 airflow_lappis/dags/data_ingest/transfere_gov/mir/programa_financeira_ingest_mir_dag.py create mode 100644 airflow_lappis/dags/data_ingest/transfere_gov/mir/programas_ingest_mir_dag.py diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/mir/notas_de_credito_ingest_mir_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/mir/notas_de_credito_ingest_mir_dag.py new file mode 100644 index 00000000..af3a107e --- /dev/null +++ b/airflow_lappis/dags/data_ingest/transfere_gov/mir/notas_de_credito_ingest_mir_dag.py @@ -0,0 +1,52 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule +from postgres_helpers import get_postgres_conn +from cliente_postgres import ClientPostgresDB +from cliente_ted import ClienteTed + + +@dag( + schedule_interval=get_dynamic_schedule("notas_de_credito_ingest_mir_dag"), + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Mateus", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["notas de credito", "ted_api", "MIR"], +) +def notas_de_credito_mir_dag() -> None: + @task + def fetch_and_store_notas_de_credito() -> None: + logging.info("Iniciando fetch_and_store_notas_de_credito") + + api = ClienteTed() + postgres_conn_str = get_postgres_conn("postgres_mir") + db = ClientPostgresDB(postgres_conn_str) + id_planos_acao = db.get_id_planos_acao() + + for id_plano_acao in id_planos_acao: + notas_de_credito = api.get_notas_de_credito_by_id_plano_acao(id_plano_acao) + if notas_de_credito: + for nota in notas_de_credito: + nota["dt_ingest"] = datetime.now().isoformat() + + db.insert_data( + notas_de_credito, + "notas_de_credito", + conflict_fields=["id_nota"], + primary_key=["id_nota"], + schema="transfere_gov", + ) + else: + logging.warning( + f"Nenhuma nota de crédito encontrada plano de ação {id_plano_acao}" + ) + + fetch_and_store_notas_de_credito() + + +dag_instance = notas_de_credito_mir_dag() diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/mir/plano_acao_ingest_mir_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/mir/plano_acao_ingest_mir_dag.py new file mode 100644 index 00000000..92fdee4e --- /dev/null +++ b/airflow_lappis/dags/data_ingest/transfere_gov/mir/plano_acao_ingest_mir_dag.py @@ -0,0 +1,57 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule +from postgres_helpers import get_postgres_conn +from cliente_ted import ClienteTed +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval=get_dynamic_schedule("plano_acao_ingest_mir_dag"), + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Mateus", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["ted_api", "planos_acao", "MIR"], +) +def api_planos_acao_mir_dag() -> None: + + @task + def fetch_and_store_planos_acao() -> None: + logging.info("Starting api_planos_acao_dag DAG") + api = ClienteTed() + postgres_conn_str = get_postgres_conn("postgres_mir") + db = ClientPostgresDB(postgres_conn_str) + id_programas = db.get_id_programas() + + total_processed = 0 + for id_programa in id_programas: + planos_acao_data = api.get_planos_acao_by_id_programa(id_programa) + if planos_acao_data: + for plano in planos_acao_data: + plano["dt_ingest"] = datetime.now().isoformat() + + db.insert_data( + planos_acao_data, + "planos_acao", + primary_key=["id_plano_acao"], + conflict_fields=["id_plano_acao"], + schema="transfere_gov", + ) + + total_processed += 1 + if total_processed % 10 == 0: + logging.info(f"Processed {total_processed} planos de ação") + else: + logging.warning(f"No planos de ação found for id_programa: {id_programa}") + + logging.info(f"Completed processing {total_processed} planos de ação") + + fetch_and_store_planos_acao() + + +dag_instance = api_planos_acao_mir_dag() diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/mir/programa_financeira_ingest_mir_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/mir/programa_financeira_ingest_mir_dag.py new file mode 100644 index 00000000..5124716a --- /dev/null +++ b/airflow_lappis/dags/data_ingest/transfere_gov/mir/programa_financeira_ingest_mir_dag.py @@ -0,0 +1,56 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule +from postgres_helpers import get_postgres_conn +from cliente_postgres import ClientPostgresDB +from cliente_ted import ClienteTed + + +@dag( + schedule_interval=get_dynamic_schedule("programacao_financeira_ingest_mir_dag"), + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Mateus", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["programacao_financeira", "ted_api", "MIR"], +) +def programacao_financeira_mir_dag() -> None: + @task + def fetch_and_store_programacao_financeira() -> None: + logging.info("Iniciando fetch_and_store_programacao_financeira") + + api = ClienteTed() + postgres_conn_str = get_postgres_conn("postgres_mir") + db = ClientPostgresDB(postgres_conn_str) + id_planos_acao = db.get_id_planos_acao() + + for id_plano_acao in id_planos_acao: + programacao_financeira = api.get_programacao_financeira_by_id_plano_acao( + id_plano_acao + ) + if programacao_financeira: + # Adicionar dt_ingest a cada item + for item in programacao_financeira: + item["dt_ingest"] = datetime.now().isoformat() + + db.insert_data( + programacao_financeira, + "programacao_financeira", + conflict_fields=["id_programacao"], + primary_key=["id_programacao"], + schema="transfere_gov", + ) + else: + logging.warning( + f"Nenhuma programação financeira encontrada " + f"plano de ação {id_plano_acao}" + ) + + fetch_and_store_programacao_financeira() + + +dag_instance = programacao_financeira_mir_dag() diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/mir/programas_ingest_mir_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/mir/programas_ingest_mir_dag.py new file mode 100644 index 00000000..ee878c48 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/transfere_gov/mir/programas_ingest_mir_dag.py @@ -0,0 +1,55 @@ +import logging +from airflow.decorators import dag, task +from airflow.models import Variable +from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule +from postgres_helpers import get_postgres_conn +from cliente_ted import ClienteTed +from cliente_postgres import ClientPostgresDB + +@dag( + schedule_interval=get_dynamic_schedule("api_programas_ted_dag"), + start_date=datetime(2023, 1, 1), + catchup=False, + default_args={ + "owner": "Mateus", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["ted_api", "programas", "MIR"], +) +def api_programas_ted_dag() -> None: + + @task + def fetch_and_ingest_programas() -> None: + logging.info("Iniciando extração de programas") + + api = ClienteTed() + postgres_conn_str = get_postgres_conn("postgres_mir") + db = ClientPostgresDB(postgres_conn_str) + + sigla_alvo = Variable.get("airflow_orgao_ted", default_var="MIR") + + logging.info(f"Filtrando programas pela sigla: {sigla_alvo}") + programas_data = api.get_programas_by_sigla_unidade_descentralizadora(sigla_alvo) + + if not programas_data: + logging.warning(f"Nenhum dado retornado para a sigla {sigla_alvo}") + return + + for programa in programas_data: + programa["dt_ingest"] = datetime.now().isoformat() + + db.insert_data( + programas_data, + "programas", + primary_key=["id_programa"], + conflict_fields=["id_programa"], + schema="transfere_gov", + ) + + logging.info(f"Sucesso: {len(programas_data)} programas inseridos/atualizados para {sigla_alvo}") + + fetch_and_ingest_programas() + +dag_instance = api_programas_ted_dag() \ No newline at end of file diff --git a/airflow_lappis/plugins/cliente_ted.py b/airflow_lappis/plugins/cliente_ted.py index cb09a0e0..2c6f2c01 100644 --- a/airflow_lappis/plugins/cliente_ted.py +++ b/airflow_lappis/plugins/cliente_ted.py @@ -130,3 +130,52 @@ def get_programacao_financeira_by_id_plano_acao( else: logging.warning(f"Falha ao buscar programação financeira - Status: {status}") return None + + def get_todos_programas(self, limit: int = 1000, offset: int = 0) -> list | None: + """ + Função ATÔMICA: Busca uma única 'fatia' (página) de programas. + """ + headers = { + **self.BASE_HEADER, + "Range-Unit": "items", + "Range": f"{offset}-{offset + limit - 1}" + } + + endpoint = "programa" + logging.info(f"[cliente_ted.py] Fetching programas (offset: {offset}, limit: {limit})") + + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=headers + ) + + if status in http.HTTPStatus.OK and isinstance(data, list): + logging.info(f"[cliente_ted.py] Sucesso ao buscar {len(data)} programas no offset {offset}.") + return data + else: + logging.error(f"[cliente_ted.py] Erro ao buscar programas. Status: {status}") + return None + + def get_all_programas(self, limit: int = 1000) -> list: + """ + Itera por todas as fatias de dados até o fim. + Segue a lógica da 'get_all_deputados'. + """ + all_programas = [] + current_offset = 0 + + while True: + programas = self.get_todos_programas(limit=limit, offset=current_offset) + + if not programas: + break + + all_programas.extend(programas) + + if len(programas) < limit: + logging.info("[cliente_ted.py] Última página alcançada.") + break + + current_offset += limit + + logging.info(f"[cliente_ted.py] Carga completa finalizada. Total: {len(all_programas)} programas.") + return all_programas \ No newline at end of file From 3362b41fa333502cf0ec67581ad3d34702f912fb Mon Sep 17 00:00:00 2001 From: Mateushqms Date: Mon, 16 Mar 2026 14:52:04 -0300 Subject: [PATCH 215/317] refactor: ajustes para nao dar erro no sonar --- .../transfere_gov/mir/notas_de_credito_ingest_mir_dag.py | 3 +-- .../data_ingest/transfere_gov/mir/plano_acao_ingest_mir_dag.py | 3 +-- .../transfere_gov/mir/programa_financeira_ingest_mir_dag.py | 3 +-- .../data_ingest/transfere_gov/mir/programas_ingest_mir_dag.py | 2 +- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/mir/notas_de_credito_ingest_mir_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/mir/notas_de_credito_ingest_mir_dag.py index af3a107e..a68379bc 100644 --- a/airflow_lappis/dags/data_ingest/transfere_gov/mir/notas_de_credito_ingest_mir_dag.py +++ b/airflow_lappis/dags/data_ingest/transfere_gov/mir/notas_de_credito_ingest_mir_dag.py @@ -48,5 +48,4 @@ def fetch_and_store_notas_de_credito() -> None: fetch_and_store_notas_de_credito() - -dag_instance = notas_de_credito_mir_dag() +notas_de_credito_mir_dag() diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/mir/plano_acao_ingest_mir_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/mir/plano_acao_ingest_mir_dag.py index 92fdee4e..b07eba23 100644 --- a/airflow_lappis/dags/data_ingest/transfere_gov/mir/plano_acao_ingest_mir_dag.py +++ b/airflow_lappis/dags/data_ingest/transfere_gov/mir/plano_acao_ingest_mir_dag.py @@ -53,5 +53,4 @@ def fetch_and_store_planos_acao() -> None: fetch_and_store_planos_acao() - -dag_instance = api_planos_acao_mir_dag() +api_planos_acao_mir_dag() \ No newline at end of file diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/mir/programa_financeira_ingest_mir_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/mir/programa_financeira_ingest_mir_dag.py index 5124716a..7d1a19f9 100644 --- a/airflow_lappis/dags/data_ingest/transfere_gov/mir/programa_financeira_ingest_mir_dag.py +++ b/airflow_lappis/dags/data_ingest/transfere_gov/mir/programa_financeira_ingest_mir_dag.py @@ -52,5 +52,4 @@ def fetch_and_store_programacao_financeira() -> None: fetch_and_store_programacao_financeira() - -dag_instance = programacao_financeira_mir_dag() +programacao_financeira_mir_dag() \ No newline at end of file diff --git a/airflow_lappis/dags/data_ingest/transfere_gov/mir/programas_ingest_mir_dag.py b/airflow_lappis/dags/data_ingest/transfere_gov/mir/programas_ingest_mir_dag.py index ee878c48..27ed8436 100644 --- a/airflow_lappis/dags/data_ingest/transfere_gov/mir/programas_ingest_mir_dag.py +++ b/airflow_lappis/dags/data_ingest/transfere_gov/mir/programas_ingest_mir_dag.py @@ -52,4 +52,4 @@ def fetch_and_ingest_programas() -> None: fetch_and_ingest_programas() -dag_instance = api_programas_ted_dag() \ No newline at end of file +api_programas_ted_dag() \ No newline at end of file From 355d508257aed1a8399577f2f2288bfeedd73aac Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Fri, 13 Mar 2026 11:06:44 -0300 Subject: [PATCH 216/317] feat(dag): siafi email empenhos parlamentares --- ...penhos_tesouro_parlamentares_ingest_dag.py | 158 ++++++++++++++++++ airflow_lappis/plugins/cliente_email.py | 29 +++- 2 files changed, 178 insertions(+), 9 deletions(-) create mode 100644 airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_parlamentares_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_parlamentares_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_parlamentares_ingest_dag.py new file mode 100644 index 00000000..84bc0ee2 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_parlamentares_ingest_dag.py @@ -0,0 +1,158 @@ +from typing import Dict, Any, Optional +from airflow import DAG +from airflow.operators.python import PythonOperator +from airflow.models import Variable +from datetime import datetime, timedelta +import logging +import json +from schedule_loader import get_dynamic_schedule +from cliente_email import fetch_and_process_email +from cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn +import pandas as pd +import io + +# Configurações básicas da DAG +default_args = { + "owner": "Tiago", + "depends_on_past": False, + "retries": 1, + "retry_delay": timedelta(minutes=5), +} + +COLUMN_MAPPING = { + 0: "programa_governo", + 1: "programa_governo_descricao", + 2: "acao_governo", + 3: "acao_governo_descricao", + 4: "emissao_mes", + 5: "emissao_dia", + 6: "ne_ccor", + 7: "ne_num_processo", + 8: "ne_info_complementar", + 9: "ne_ccor_descricao", + 10: "doc_observacao", + 11: "natureza_despesa", + 12: "natureza_despesa_descricao", + 13: "ne_ccor_favorecido", + 14: "ne_ccor_favorecido_descricao", + 15: "ne_ccor_ano_emissao", + 16: "ptres", + 17: "fonte_recursos_detalhada", + 18: "fonte_recursos_detalhada_descricao", + 19: "despesas_empenhadas", + 20: "despesas_liquidadas", + 21: "despesas_pagas", + 22: "restos_a_pagar_inscritos", + 23: "restos_a_pagar_pagos", +} + +EMAIL_SUBJECT = "notas_de_empenho_ano_atual" +SKIPROWS = 8 + +# Configurações da DAG +with DAG( + dag_id="email_tesouro_parlamentares_ingest", + default_args=default_args, + description="Processa anexos dos empenhos vindo do email, formata e insere no db", + schedule_interval=get_dynamic_schedule("empenhos_tesouro_parlamentares_ingest_dag"), + start_date=datetime(2023, 12, 1), + catchup=False, + tags=["MIR", "email", "empenhos", "tesouro", "parlamentares"], +) as dag: + + def process_email_data(**context: Dict[str, Any]) -> Optional[Any]: + creds = json.loads(Variable.get("email_credentials")) + + EMAIL = creds["email"] + PASSWORD = creds["password"] + IMAP_SERVER = creds["imap_server"] + SENDER_EMAIL = creds["sender_email"] + + try: + logging.info("Iniciando o processamento dos emails...") + csv_data = fetch_and_process_email( + IMAP_SERVER, + EMAIL, + PASSWORD, + SENDER_EMAIL, + EMAIL_SUBJECT, + COLUMN_MAPPING, + skiprows=SKIPROWS, + ) + if not csv_data: + logging.warning("Nenhum e-mail encontrado com o assunto esperado.") + return None + + logging.info( + "CSV processado com sucesso. Dados encontrados: %s", len(csv_data) + ) + return csv_data + except Exception as e: + logging.error("Erro no processamento dos emails: %s", str(e)) + raise + + def insert_data_to_db(**context: Dict[str, Any]) -> None: + """ + Função para inserir os dados no banco de dados. + Os dados do CSV são recuperados do XCom. + """ + try: + task_instance: Any = context["ti"] + csv_data: Any = task_instance.xcom_pull(task_ids="process_emails") + + if not csv_data: + logging.warning("Nenhum dado para inserir no banco.") + return + + df = pd.read_csv(io.StringIO(csv_data)) + df = df[df["ne_ccor_ano_emissao"].astype(str).str.startswith("20")] + data = df.to_dict(orient="records") + + # Adicionar dt_ingest a cada registro + for record in data: + record["dt_ingest"] = datetime.now().isoformat() + + postgres_conn_str = get_postgres_conn("postgres_mir") + db = ClientPostgresDB(postgres_conn_str) + + unique_key = [ + "ne_ccor", + "natureza_despesa", + "doc_observacao", + "ne_ccor_ano_emissao", + "emissao_dia", + "emissao_mes", + "despesas_empenhadas", + "despesas_liquidadas", + "despesas_pagas", + ] + + db.insert_data( + data, + "empenhos_tesouro_parlamentares", + conflict_fields=unique_key, + primary_key=unique_key, + schema="siafi", + ) + logging.info("Dados inseridos com sucesso no banco de dados.") + except Exception as e: + logging.error("Erro ao inserir dados no banco: %s", str(e)) + raise + + # Tarefa 1: Processar os e-mails e retornar CSV + process_emails_task = PythonOperator( + task_id="process_emails", + python_callable=process_email_data, + provide_context=True, + ) + + # Tarefa 2: Inserir os dados no banco de dados + insert_to_db_task = PythonOperator( + task_id="insert_to_db", + python_callable=insert_data_to_db, + provide_context=True, + ) + + # Fluxo da DAG + process_emails_task >> insert_to_db_task diff --git a/airflow_lappis/plugins/cliente_email.py b/airflow_lappis/plugins/cliente_email.py index b85ded3d..f05e2bc7 100755 --- a/airflow_lappis/plugins/cliente_email.py +++ b/airflow_lappis/plugins/cliente_email.py @@ -45,15 +45,16 @@ def extract_csv_from_zip( def fetch_email_with_zip( imap_server: str, email: str, password: str, sender_email: str, subject: str -) -> Optional[bytes]: - """Busca o primeiro e-mail do dia atual com um anexo ZIP.""" +) -> List[bytes]: + """Busca todos os e-mails do dia atual e retorna todos os anexos ZIP.""" today = datetime.now(pytz.timezone("America/Sao_Paulo")).date() + zip_payloads: List[bytes] = [] with MailBox(imap_server).login(email, password) as mailbox: for msg in mailbox.fetch(AND(date=today, from_=sender_email, subject=subject)): for attachment in msg.attachments: - if attachment.filename.endswith(".zip"): - return cast(bytes, attachment.payload) - return None + if attachment.filename.lower().endswith(".zip"): + zip_payloads.append(cast(bytes, attachment.payload)) + return zip_payloads def fetch_and_process_email( @@ -65,15 +66,25 @@ def fetch_and_process_email( column_mapping: dict, skiprows: int = 0, ) -> Optional[str]: - """Busca e processa o primeiro e-mail com um ZIP contendo um CSV formatado.""" + """Busca e processa e-mails do dia, extraindo CSVs de todos os ZIPs anexados.""" try: - zip_payload = fetch_email_with_zip( + zip_payloads = fetch_email_with_zip( imap_server, email, password, sender_email, subject ) - if zip_payload: + if not zip_payloads: + logging.warning("Nenhum anexo ZIP encontrado.") + return None + + dataframes: List[pd.DataFrame] = [] + for zip_payload in zip_payloads: csv_data = extract_csv_from_zip(zip_payload, column_mapping, skiprows) if csv_data is not None: - return csv_data.to_csv(index=False) + dataframes.append(csv_data) + + if dataframes: + combined_df = pd.concat(dataframes, ignore_index=True) + return combined_df.to_csv(index=False) + logging.warning("Nenhum CSV processado.") except Exception as e: logging.error(f"Erro ao processar e-mails: {e}") From 1ac8f637f9c03e504402b8e4544fa45a10130f5d Mon Sep 17 00:00:00 2001 From: Mateushqms Date: Mon, 16 Mar 2026 18:15:22 -0300 Subject: [PATCH 217/317] =?UTF-8?q?refactor:=20otimizacao=20das=20dags=20q?= =?UTF-8?q?ue=20faziam=20processos=20de=20chunks=20desnecess=C3=A1rios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../executor_especial_ingest_dag.py | 129 ++++---------- .../reltorio_gestao_ingest.py | 166 ++---------------- .../plugins/cliente_transferegov_emendas.py | 133 +++++++------- 3 files changed, 121 insertions(+), 307 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/executor_especial_ingest_dag.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/executor_especial_ingest_dag.py index faa19c2c..a2c7cbc5 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/executor_especial_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/executor_especial_ingest_dag.py @@ -6,18 +6,6 @@ from cliente_postgres import ClientPostgresDB -from typing import Iterator - - -CHUNK_SIZE = 200 - - -def chunk_list(lst: list, size: int) -> Iterator[list]: - """Divide uma lista em partes menores (chunks) de tamanho fixo.""" - for i in range(0, len(lst), size): - yield lst[i : i + size] - - @dag( schedule_interval="@daily", start_date=datetime(2023, 1, 1), @@ -30,96 +18,57 @@ def chunk_list(lst: list, size: int) -> Iterator[list]: tags=["transfere_gov_api", "planos_acao_especiais", "MIR"], ) def api_executor_especial_dag() -> None: + """DAG para buscar e armazenar executores especiais do Transfere Gov de forma massiva.""" @task - def fetch_planos_acao() -> list: - """ - Busca todos os IDs dos planos de ação na base Postgres. - Cada ID será posteriormente usado para buscar executores na API. - """ - logging.info("[executor_especial] Buscando planos de ação...") - - db = ClientPostgresDB(get_postgres_conn("postgres_mir")) - - query = """ - SELECT DISTINCT id_plano_acao - FROM transferegov_emendas.planos_acao_especiais - """ - - result = db.execute_query(query) - - # Converte lista de tuplas [(123,), (456,)] para [123, 456] - return [row[0] for row in result] - - @task - def split_chunks(planos_ids: list) -> list: - """ - Divide a lista de planos em múltiplos chunks para paralelizar via Airflow. - Ex.: 48k IDs → 240 tasks (se CHUNK_SIZE = 200) - """ - return list(chunk_list(planos_ids, CHUNK_SIZE)) + def fetch_and_store_executores_especiais() -> None: + logging.info( + "[executores_especiais_ingest_dag.py] Iniciando extração massiva de executores especiais" + ) - @task - def process_chunk(chunk: list) -> None: - """ - Para cada chunk: - - Busca executores de cada plano de ação na API do TransfereGov - - Enrica dados com timestamp - - Remove duplicados - - Insere no Postgres com UPSERT - """ api = ClienteTransfereGov() - db = ClientPostgresDB(get_postgres_conn("postgres_mir")) - - timestamp = datetime.now().isoformat() - all_executores = [] + postgres_conn_str = get_postgres_conn("postgres_mir") + db = ClientPostgresDB(postgres_conn_str) - # Processa cada plano de ação pertencente a este chunk - for plano_id in chunk: - logging.info(f"[executor_especial] Buscando executores do plano {plano_id}") + executores_data = api.get_all_executores_especiais(limit=1000) - executores = api.get_all_executores_especiais_by_plano_acao(plano_id) + if executores_data and len(executores_data) > 0: + timestamp_atual = datetime.now().isoformat() - if not executores: - # Não há executores para este plano -> pula - continue + unique = {} + for row in executores_data: + key = (row["id_plano_acao"], row["id_executor"]) + unique[key] = row # se já existir, substitui e mantém apenas 1 - # Adiciona metadados aos registros - for executor in executores: - executor["id_plano_acao"] = plano_id - executor["dt_ingest"] = timestamp + executores_data = list(unique.values()) - all_executores.extend(executores) + for executor in executores_data: + executor["dt_ingest"] = timestamp_atual - if not all_executores: - logging.info("[executor_especial] Chunk sem resultados.") - return + logging.info( + f"[executores_especiais_ingest_dag.py] Inserindo {len(executores_data)} " + "executores no schema transferegov_emendas" + ) - logging.info( - f"[executor_especial] Inserindo {len(all_executores)} executores no Postgres." - ) - - # Remove duplicatas usando a chave (id_plano_acao, id_executor) - unique = {} - for row in all_executores: - key = (row["id_plano_acao"], row["id_executor"]) - unique[key] = row # se já existir, substitui e mantém apenas 1 + # Inserção/Update (Upsert) + db.insert_data( + executores_data, + "executor_especial", + conflict_fields=["id_plano_acao", "id_executor"], + primary_key=["id_plano_acao", "id_executor"], + schema="transferegov_emendas", + ) - all_executores = list(unique.values()) - - # Insere com UPSERT no Postgres - db.insert_data( - all_executores, - table_name="executor_especial", - conflict_fields=["id_plano_acao", "id_executor"], - primary_key=["id_plano_acao", "id_executor"], - schema="transferegov_emendas", - ) + logging.info( + f"[executores_especiais_ingest_dag.py] Concluído. Total de " + f"{len(executores_data)} executores inseridos/atualizados" + ) + else: + logging.warning( + "[executores_especiais_ingest_dag.py] Nenhum executor especial encontrado na API" + ) - ids = fetch_planos_acao() # Busca todos os planos de ação - chunks = split_chunks(ids) # Divide em várias listas menores - process_chunk.expand(chunk=chunks) # Executa cada chunk em tasks paralelas + fetch_and_store_executores_especiais() -# Instancia a DAG para o Airflow carregar -dag_instance = api_executor_especial_dag() +api_executor_especial_dag() diff --git a/airflow_lappis/dags/data_ingest/transferegov_emendas/reltorio_gestao_ingest.py b/airflow_lappis/dags/data_ingest/transferegov_emendas/reltorio_gestao_ingest.py index 9e7196f1..011fb206 100644 --- a/airflow_lappis/dags/data_ingest/transferegov_emendas/reltorio_gestao_ingest.py +++ b/airflow_lappis/dags/data_ingest/transferegov_emendas/reltorio_gestao_ingest.py @@ -1,187 +1,57 @@ import logging from airflow.decorators import dag, task from datetime import datetime -from typing import Iterator - -# Imports dos seus módulos personalizados from postgres_helpers import get_postgres_conn from cliente_transferegov_emendas import ClienteTransfereGov from cliente_postgres import ClientPostgresDB -CHUNK_SIZE = 200 - - -def chunk_list(lst: list, size: int) -> Iterator[list]: - """Divide uma lista em partes menores (chunks) de tamanho fixo.""" - for i in range(0, len(lst), size): - yield lst[i : i + size] - - -def _fetch_relatorios_for_plano( - api: ClienteTransfereGov, plano_id: int, timestamp: str -) -> list: - """Busca relatórios de um plano e adiciona metadados.""" - logging.info(f"[relatorio_gestao] Buscando relatórios do plano {plano_id}") - - relatorios = api.get_all_relatorio_gestao_especial_by_plano_acao(plano_id) - - if not relatorios: - return [] - - # Adiciona metadados aos registros - for item in relatorios: - # Garante que o ID do plano está no registro - # (caso a API não retorne no corpo) - item["id_plano_acao"] = plano_id - item["dt_ingest"] = timestamp - - return relatorios - - -def _remove_duplicates(all_relatorios: list) -> list: - """Remove duplicatas usando id_relatorio_gestao como chave.""" - unique = {} - for row in all_relatorios: - # Baseado na imagem do Swagger, a PK parece ser - # id_relatorio_gestao. Se quiser garantir unicidade por plano, - # pode usar tupla (id_plano_acao, id_relatorio_gestao) - key = row.get("id_relatorio_gestao") - if key: - unique[key] = row - else: - # Fallback caso venha sem ID (improvável, mas seguro) - logging.warning(f"Registro sem id_relatorio_gestao encontrado: {row}") - - return list(unique.values()) - - @dag( schedule_interval="@daily", start_date=datetime(2023, 1, 1), catchup=False, default_args={ "owner": "Mateus", - "retries": 0, - # "retry_delay": timedelta(minutes=5), + "retries": 1, }, tags=["transfere_gov_api", "relatorio_gestao_especial", "MIR"], ) def api_relatorio_gestao_especial_dag() -> None: @task - def setup_table() -> None: - """ - Cria a tabela antes de qualquer processamento paralelo. - Evita race conditions nas tasks paralelas. - """ - logging.info("[relatorio_gestao] Configurando tabela...") - - db = ClientPostgresDB(get_postgres_conn("postgres_mir")) + def fetch_and_store_relatorios() -> None: + logging.info("[relatorio_gestao] Iniciando extração massiva global...") - # Remove a tabela antiga se existir (para garantir estrutura correta) - db.drop_table_if_exists( - table_name="relatorio_gestao_especial", schema="transferegov_emendas" - ) - - # Cria tabela com TODOS os campos que virão da API - # Baseado na estrutura real retornada pela API TransfereGov - sample_data = { - "id_relatorio_gestao": "0", - "situacao_relatorio_gestao": "", - "parecer_relatorio_gestao": "", - "id_plano_acao": "0", - "dt_ingest": datetime.now().isoformat(), - } - - db.create_table_if_not_exists( - sample_data, - table_name="relatorio_gestao_especial", - primary_key=["id_relatorio_gestao"], - schema="transferegov_emendas", - ) - - logging.info("[relatorio_gestao] Tabela configurada com sucesso") - - @task - def fetch_planos_acao() -> list: - """ - Busca todos os IDs dos planos de ação na base Postgres. - Usa a mesma tabela base da DAG anterior para obter os IDs. - """ - logging.info("[relatorio_gestao] Buscando planos de ação...") - - db = ClientPostgresDB(get_postgres_conn("postgres_mir")) - - # Mantive a mesma query da DAG anterior, pois o filtro é por plano de ação - query = """ - SELECT DISTINCT id_plano_acao - FROM transferegov_emendas.planos_acao_especiais - """ - - result = db.execute_query(query) - - # Converte lista de tuplas [(123,), (456,)] para [123, 456] - return [row[0] for row in result] - - @task - def split_chunks(planos_ids: list) -> list: - """ - Divide a lista de planos em múltiplos chunks para paralelizar via Airflow. - """ - return list(chunk_list(planos_ids, CHUNK_SIZE)) - - @task - def process_chunk(chunk: list) -> None: - """ - Para cada chunk: - - Busca relatórios de gestão de cada plano de ação na API - - Enrica dados com timestamp e id_plano_acao - - Remove duplicados - - Insere no Postgres com UPSERT - """ api = ClienteTransfereGov() - db = ClientPostgresDB(get_postgres_conn("postgres_mir")) + postgres_conn_str = get_postgres_conn("postgres_mir") + db = ClientPostgresDB(postgres_conn_str) - timestamp = datetime.now().isoformat() - all_relatorios = [] + relatorios_data = api.get_all_relatorio_gestao_especial(page_size=1000) - # Processa cada plano de ação pertencente a este chunk - for plano_id in chunk: - relatorios = _fetch_relatorios_for_plano(api, plano_id, timestamp) - all_relatorios.extend(relatorios) - - if not all_relatorios: - logging.info("[relatorio_gestao] Chunk sem resultados.") + if not relatorios_data: + logging.warning("[relatorio_gestao] Nenhum relatório retornado da API.") return - logging.info( - f"[relatorio_gestao] Inserindo {len(all_relatorios)} registros no Postgres." - ) + timestamp_atual = datetime.now().isoformat() - all_relatorios_unique = _remove_duplicates(all_relatorios) + for row in relatorios_data: + row["dt_ingest"] = timestamp_atual - if not all_relatorios_unique: - return + logging.info( + f"[relatorio_gestao] Inserindo {len(relatorios_data)} registros no Postgres." + ) - # Insere com UPSERT no Postgres db.insert_data( - all_relatorios_unique, + relatorios_data, table_name="relatorio_gestao_especial", - # Define o conflito na PK da tabela conflict_fields=["id_relatorio_gestao"], primary_key=["id_relatorio_gestao"], schema="transferegov_emendas", ) - setup = setup_table() # Cria a tabela primeiro - ids = fetch_planos_acao() # Busca todos os planos de ação - chunks = split_chunks(ids) # Divide em várias listas menores + logging.info("[relatorio_gestao] Ingestão concluída com sucesso.") - # Garante que a tabela é criada antes do processamento paralelo - setup >> ids - process_chunk.expand(chunk=chunks) # Executa cada chunk em tasks paralelas + fetch_and_store_relatorios() -# Instancia a DAG -dag_instance = api_relatorio_gestao_especial_dag() +api_relatorio_gestao_especial_dag() diff --git a/airflow_lappis/plugins/cliente_transferegov_emendas.py b/airflow_lappis/plugins/cliente_transferegov_emendas.py index e8297251..3fda6ecf 100644 --- a/airflow_lappis/plugins/cliente_transferegov_emendas.py +++ b/airflow_lappis/plugins/cliente_transferegov_emendas.py @@ -186,16 +186,23 @@ def get_all_planos_acao_especiais_by_programa( ) return all_data - def get_executores_especiais_by_plano_acao( - self, id_plano_acao: int, limit: int = 1000, offset: int = 0 + def get_executores_especiais( + self, limit: int = 1000, offset: int = 0 ) -> Optional[list]: + """ + Busca uma fatia global de executores com ordenação para maior performance. + """ + endpoint = "executor_especial" - endpoint = f"executor_especial?id_plano_acao=eq.{id_plano_acao}" - params = {"select": "*", "limit": limit, "offset": offset} + params = { + "select": "*", + "order": "id_executor.asc", # Ordenação por chave primária + "limit": limit, + "offset": offset, + } logging.info( - f"[cliente_transfere_gov.py] Fetching executores especiais " - f"for id_plano_acao={id_plano_acao}, limit={limit}, offset={offset}" + f"[cliente_transfere_gov.py] Fetching executores (limit={limit}, offset={offset})" ) status, data = self.request( @@ -206,53 +213,52 @@ def get_executores_especiais_by_plano_acao( ) if status == http.HTTPStatus.OK and isinstance(data, list): - return data - else: - logging.warning( - f"[cliente_transfere_gov.py] Failed to fetch executores especiais " - f"for plano_acao {id_plano_acao} with status {status}" + logging.info( + f"[cliente_transfere_gov.py] Sucesso: {len(data)} registros retornados." ) - return None + return data - def get_all_executores_especiais_by_plano_acao( - self, id_plano_acao: int, page_size: int = 1000 - ) -> list: + logging.warning( + f"[cliente_transfere_gov.py] Falha na requisição. Status: {status}" + ) + return None + def get_all_executores_especiais(self, limit: int = 1000) -> list: + """ + Extração completa com logs de progresso detalhados. + """ all_data = [] offset = 0 page = 1 logging.info( - f"[cliente_transfere_gov.py] Starting extraction of executores especiais " - f"for plano_acao={id_plano_acao}" + "[cliente_transfere_gov.py] Iniciando extração GLOBAL de executores especiais" ) while True: - logging.info( - f"[cliente_transfere_gov.py] Fetching page {page} (offset: {offset}) " - f"for plano_acao={id_plano_acao}" - ) - - data = self.get_executores_especiais_by_plano_acao( - id_plano_acao, limit=page_size, offset=offset - ) + data = self.get_executores_especiais(limit=limit, offset=offset) if not data or len(data) == 0: + logging.info("[cliente_transfere_gov.py] Fim dos dados alcançado.") break all_data.extend(data) - if len(data) < page_size: + logging.info( + f"[cliente_transfere_gov.py] Página {page} processada. " + f"Total acumulado: {len(all_data)}" + ) + + if len(data) < limit: + logging.info("[cliente_transfere_gov.py] Última página identificada.") break - offset += page_size + offset += limit page += 1 logging.info( - f"[cliente_transfere_gov.py] Finished extraction. " - f"Total executores especiais for plano_acao {id_plano_acao}: {len(all_data)}" + f"[cliente_transfere_gov.py] Extração finalizada. Total: {len(all_data)}" ) - return all_data def get_empenhos_especiais_by_plano_acao( @@ -422,79 +428,68 @@ def get_all_empenhos_especiais_by_plano_acao( ) return all_data - def get_relatorio_gestao_especial_by_plano_acao( - self, id_plano_acao: int, limit: int = 1000, offset: int = 0 + def get_relatorio_gestao_especial( + self, limit: int = 1000, offset: int = 0 ) -> Optional[list]: - """ - Obter relatórios de gestão especial por ID do plano de ação com paginação. - Endpoint: /relatorio_gestao_especial - """ - endpoint = f"relatorio_gestao_especial?id_plano_acao=eq.{id_plano_acao}" - params = {"select": "*", "limit": limit, "offset": offset} + """Busca relatórios de gestão de forma global com paginação.""" + endpoint = "relatorio_gestao_especial" + params = { + "select": "*", + "order": "id_relatorio_gestao.asc", + "limit": limit, + "offset": offset, + } logging.info( - f"[cliente_transfere_gov.py] Fetching relatorio_gestao_especial for " - f"id_plano_acao={id_plano_acao}, limit={limit}, offset={offset}" + f"[cliente_transfere_gov.py] Fetching relatorios with limit={limit}, offset={offset}" ) status, data = self.request( - http.HTTPMethod.GET, - endpoint, - headers=self.BASE_HEADER, - params=params, + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER, params=params ) if status == http.HTTPStatus.OK and isinstance(data, list): - return data - else: - logging.warning( - f"[cliente_transfere_gov.py] Failed to fetch relatorio_gestao_especial " - f"for plano_acao {id_plano_acao} with status {status}" + logging.info( + f"[cliente_transfere_gov.py] Successfully fetched {len(data)} records" ) - return None + return data - def get_all_relatorio_gestao_especial_by_plano_acao( - self, id_plano_acao: int, page_size: int = 1000 - ) -> list: - """ - Obter TODOS os relatórios de gestão especial de um plano de ação - com paginação automática (While True). - """ + logging.warning(f"[cliente_transfere_gov.py] Failed with status: {status}") + return None + + def get_all_relatorio_gestao_especial(self, page_size: int = 1000) -> list: + """Obter todos os relatórios com paginação automática global.""" all_data = [] offset = 0 page = 1 logging.info( - f"[cliente_transfere_gov.py] Starting extraction of " - f"relatorio_gestao_especial for plano_acao={id_plano_acao}" + "[cliente_transfere_gov.py] Starting full extraction of relatorio_gestao_especial" ) while True: logging.info( - f"[cliente_transfere_gov.py] Fetching page {page} (offset: {offset}) " - f"for plano_acao={id_plano_acao}" + f"[cliente_transfere_gov.py] Fetching page {page} (offset: {offset})" ) - data = self.get_relatorio_gestao_especial_by_plano_acao( - id_plano_acao, limit=page_size, offset=offset - ) + data = self.get_relatorio_gestao_especial(limit=page_size, offset=offset) if not data or len(data) == 0: + logging.info("[cliente_transfere_gov.py] No more data received.") break all_data.extend(data) + logging.info( + f"[cliente_transfere_gov.py] Page {page} fetched. Total so far: {len(all_data)}" + ) - # Se vier menos registros que o page_size, chegamos ao fim if len(data) < page_size: + logging.info("[cliente_transfere_gov.py] Last page reached.") break offset += page_size page += 1 - logging.info( - f"[cliente_transfere_gov.py] Finished extraction. " - f"Total relatorios gestao for plano_acao {id_plano_acao}: {len(all_data)}" - ) return all_data def get_documentos_habeis_especiais( From aa7cec5e362d431f107b9de3b2aa78bb1ddf3560 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Tue, 17 Mar 2026 10:33:06 -0300 Subject: [PATCH 218/317] chore: remove testes desnecessarios --- .../mir/models/emendas_dbt/bronze/schema.yml | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/schema.yml b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/schema.yml index a020153a..ec7edf7b 100644 --- a/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/schema.yml +++ b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/schema.yml @@ -77,9 +77,6 @@ models: description: > Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. tests: - - row_count_match: - source_table: transferegov_emendas.programas_especiais - target_table: emendas.programas - verificacao_tipagem: nome_tabela: 'emendas.programas' nome_coluna: 'id_programa' @@ -235,9 +232,6 @@ models: description: > Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. tests: - - row_count_match: - source_table: transferegov_emendas.planos_acao_especiais - target_table: emendas.planos_acoes - verificacao_tipagem: nome_tabela: 'emendas.planos_acoes' nome_coluna: 'id_plano_acao' @@ -471,9 +465,6 @@ models: description: > Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. tests: - - row_count_match: - source_table: transferegov_emendas.ordens_bancarias_especiais - target_table: emendas.ordens_bancarias - verificacao_tipagem: nome_tabela: 'emendas.ordens_bancarias' nome_coluna: 'id_op_ob' @@ -540,9 +531,6 @@ models: description: > Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. tests: - - row_count_match: - source_table: transferegov_emendas.historico_pagamentos_especiais - target_table: emendas.historico_pagamentos - verificacao_tipagem: nome_tabela: 'emendas.historico_pagamentos' nome_coluna: 'id_historico_op_ob' @@ -607,9 +595,6 @@ models: description: > Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. tests: - - row_count_match: - source_table: transferegov_emendas.plano_trabalho_especial - target_table: emendas.planos_trabalho_especial - verificacao_tipagem: nome_tabela: 'emendas.planos_trabalho_especial' nome_coluna: 'id_plano_trabalho' @@ -713,9 +698,6 @@ models: description: > Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. tests: - - row_count_match: - source_table: transferegov_emendas.documentos_habeis_especiais - target_table: emendas.documentos_habeis - verificacao_tipagem: nome_tabela: 'emendas.documentos_habeis' nome_coluna: 'id_dh' @@ -817,9 +799,6 @@ models: - name: dt_ingest description: "Data e hora da ingestão dos dados na camada bronze (UTC-3)." data_tests: - - row_count_match: - source_table: transferegov_emendas.relatorio_gestao_novo_especial - target_table: emendas.relatorio_gestao_novo - verificacao_tipagem: nome_tabela: 'emendas.relatorio_gestao_novo' nome_coluna: 'id_relatorio_gestao_novo' @@ -889,9 +868,6 @@ models: description: "Data e hora (UTC-3 Brasília) da ingestão dos dados para a camada bronze." data_tests: - - row_count_match: - source_table: transferegov_emendas.executor_especial - target_table: emendas.executor - verificacao_tipagem: nome_tabela: 'emendas.executor_especial' nome_coluna: 'id_plano_acao' @@ -966,9 +942,6 @@ models: description: "Data e hora (UTC-3 Brasília) da ingestão dos dados para a camada bronze." data_tests: - - row_count_match: - source_table: transferegov_emendas.meta_especial - target_table: emendas.metas - verificacao_tipagem: nome_tabela: 'emendas.meta_especial' nome_coluna: 'id_executor' @@ -1025,9 +998,6 @@ models: description: "Data e hora (UTC-3 Brasília) da ingestão dos dados para a camada bronze." data_tests: - - row_count_match: - source_table: transferegov_emendas.finalidade_especial - target_table: emendas.finalidades - verificacao_tipagem: nome_tabela: 'emendas.finalidade_especial' nome_coluna: 'id_executor' From 5127bf87d60353a27da671e8bb7b90444eb1a199 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Tue, 17 Mar 2026 10:44:55 -0300 Subject: [PATCH 219/317] =?UTF-8?q?chore:=20adapta=20tests=20para=20vers?= =?UTF-8?q?=C3=A3o=20correta=20do=20dbt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dbt/mir/models/deputados_dbt/bronze/schema.yaml | 2 +- .../dags/dbt/mir/models/emendas_dbt/bronze/schema.yml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/airflow_lappis/dags/dbt/mir/models/deputados_dbt/bronze/schema.yaml b/airflow_lappis/dags/dbt/mir/models/deputados_dbt/bronze/schema.yaml index da1f6aef..852b4e46 100644 --- a/airflow_lappis/dags/dbt/mir/models/deputados_dbt/bronze/schema.yaml +++ b/airflow_lappis/dags/dbt/mir/models/deputados_dbt/bronze/schema.yaml @@ -50,7 +50,7 @@ models: - name: dt_ingest description: > Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. - data_tests: + tests: - verificacao_tipagem: nome_tabela: 'deputados.deputados' nome_coluna: 'id' diff --git a/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/schema.yml b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/schema.yml index ec7edf7b..fc46cbc6 100644 --- a/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/schema.yml +++ b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/schema.yml @@ -763,7 +763,7 @@ models: description: "Parecer do Relatório de Gestão" - name: id_plano_acao description: "Identificador Único do Plano de Ação (PA)." - data_tests: + tests: - verificacao_tipagem: nome_tabela: 'emendas.relatorio_gestao' nome_coluna: 'id_relatorio_gestao' @@ -798,7 +798,7 @@ models: description: "Chave estrangeira que vincula o relatório ao Plano de Ação correspondente." - name: dt_ingest description: "Data e hora da ingestão dos dados na camada bronze (UTC-3)." - data_tests: + tests: - verificacao_tipagem: nome_tabela: 'emendas.relatorio_gestao_novo' nome_coluna: 'id_relatorio_gestao_novo' @@ -867,7 +867,7 @@ models: - name: dt_ingest description: "Data e hora (UTC-3 Brasília) da ingestão dos dados para a camada bronze." - data_tests: + tests: - verificacao_tipagem: nome_tabela: 'emendas.executor_especial' nome_coluna: 'id_plano_acao' @@ -941,7 +941,7 @@ models: - name: dt_ingest description: "Data e hora (UTC-3 Brasília) da ingestão dos dados para a camada bronze." - data_tests: + tests: - verificacao_tipagem: nome_tabela: 'emendas.meta_especial' nome_coluna: 'id_executor' @@ -997,7 +997,7 @@ models: - name: dt_ingest description: "Data e hora (UTC-3 Brasília) da ingestão dos dados para a camada bronze." - data_tests: + tests: - verificacao_tipagem: nome_tabela: 'emendas.finalidade_especial' nome_coluna: 'id_executor' From e892d363b53a59c434c753c42796c32d9b51e974 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Wed, 18 Mar 2026 11:20:10 -0300 Subject: [PATCH 220/317] feat: fallback csv vazio ingest --- ...penhos_tesouro_parlamentares_ingest_dag.py | 5 ++- airflow_lappis/plugins/cliente_email.py | 34 ++++++++++++++++--- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_parlamentares_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_parlamentares_ingest_dag.py index 84bc0ee2..b7161bb4 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_parlamentares_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_parlamentares_ingest_dag.py @@ -81,7 +81,10 @@ def process_email_data(**context: Dict[str, Any]) -> Optional[Any]: skiprows=SKIPROWS, ) if not csv_data: - logging.warning("Nenhum e-mail encontrado com o assunto esperado.") + logging.warning( + "Nenhum CSV valido foi extraido dos e-mails encontrados " + "para o assunto esperado." + ) return None logging.info( diff --git a/airflow_lappis/plugins/cliente_email.py b/airflow_lappis/plugins/cliente_email.py index f05e2bc7..7fa5ce41 100755 --- a/airflow_lappis/plugins/cliente_email.py +++ b/airflow_lappis/plugins/cliente_email.py @@ -3,6 +3,7 @@ import zipfile from typing import Optional, cast, List, Dict import pandas as pd +from pandas.errors import EmptyDataError from imap_tools import MailBox, AND import chardet from datetime import datetime @@ -35,11 +36,27 @@ def extract_csv_from_zip( """Extrai e formata o primeiro arquivo CSV encontrado em um ZIP.""" with zipfile.ZipFile(io.BytesIO(zip_payload)) as zip_file: for file_name in zip_file.namelist(): - if file_name.endswith(".csv"): + if file_name.lower().endswith(".csv"): raw_data = zip_file.read(file_name) encoding = chardet.detect(raw_data)["encoding"] - decoded_data = raw_data.decode(encoding) - return format_csv(decoded_data, column_mapping, skiprows) + + if not raw_data.strip(): + logging.warning("CSV vazio no anexo ZIP: %s", file_name) + continue + + try: + decoded_data = raw_data.decode(encoding or "utf-8", errors="replace") + if not decoded_data.strip(): + logging.warning("CSV vazio no anexo ZIP: %s", file_name) + continue + return format_csv(decoded_data, column_mapping, skiprows) + except EmptyDataError: + logging.warning( + "CSV sem colunas apos skiprows=%s no arquivo: %s", + skiprows, + file_name, + ) + continue return None @@ -75,11 +92,18 @@ def fetch_and_process_email( logging.warning("Nenhum anexo ZIP encontrado.") return None + logging.info("Total de anexos ZIP encontrados: %s", len(zip_payloads)) + dataframes: List[pd.DataFrame] = [] - for zip_payload in zip_payloads: + for idx, zip_payload in enumerate(zip_payloads, start=1): csv_data = extract_csv_from_zip(zip_payload, column_mapping, skiprows) if csv_data is not None: dataframes.append(csv_data) + else: + logging.warning( + "ZIP %s ignorado por nao conter CSV valido.", + idx, + ) if dataframes: combined_df = pd.concat(dataframes, ignore_index=True) @@ -88,4 +112,4 @@ def fetch_and_process_email( logging.warning("Nenhum CSV processado.") except Exception as e: logging.error(f"Erro ao processar e-mails: {e}") - return None + raise From ee444beb1b64d677a12a2a1ed9ebaf8155b35175 Mon Sep 17 00:00:00 2001 From: Mateushqms Date: Wed, 18 Mar 2026 13:39:41 -0300 Subject: [PATCH 221/317] feat: camada bronze empenhos ted --- ...penhos_tesouro_parlamentares_ingest_dag.py | 0 airflow_lappis/dags/dbt/mir/dbt_project.yml | 3 + .../bronze/empenhos_tesouro_ted.sql | 37 +++++++ .../empenhos_ted_dbt/bronze/schema.yaml | 102 ++++++++++++++++++ .../dags/dbt/mir/models/sources.yml | 5 + 5 files changed, 147 insertions(+) rename airflow_lappis/dags/data_ingest/tesouro_gerencial/{ => mir}/empenhos_tesouro_parlamentares_ingest_dag.py (100%) create mode 100644 airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/empenhos_tesouro_ted.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/schema.yaml diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_parlamentares_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/empenhos_tesouro_parlamentares_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_parlamentares_ingest_dag.py rename to airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/empenhos_tesouro_parlamentares_ingest_dag.py diff --git a/airflow_lappis/dags/dbt/mir/dbt_project.yml b/airflow_lappis/dags/dbt/mir/dbt_project.yml index 01211c0c..8943454b 100755 --- a/airflow_lappis/dags/dbt/mir/dbt_project.yml +++ b/airflow_lappis/dags/dbt/mir/dbt_project.yml @@ -30,6 +30,9 @@ models: senadores_dbt: +materialized: table +schema: senadores + empenhos_ted_dbt: + +materialized: table + +schema: siafi_dbt on-run-start: diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/empenhos_tesouro_ted.sql b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/empenhos_tesouro_ted.sql new file mode 100644 index 00000000..72a6916f --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/empenhos_tesouro_ted.sql @@ -0,0 +1,37 @@ +{{ config(materialized="table") }} + + +with + empenhos_teds_raw as ( + select + programa_governo::text as programa_governo, + programa_governo_descricao::text as programa_governo_descricao, + acao_governo::text as acao_governo, + acao_governo_descricao::text as acao_governo_descricao, + emissao_mes::text as emissao_mes, + emissao_dia::text as emissao_dia, + ne_ccor::text as ne_ccor, + regexp_replace(ne_num_processo, '[./-]', '', 'g') as ne_num_processo, + ne_info_complementar::text as ne_info_complementar, + ne_ccor_descricao::text as ne_ccor_descricao, + doc_observacao::text as doc_observacao, + natureza_despesa::text as natureza_despesa, + natureza_despesa_descricao::text as natureza_despesa_descricao, + upper(ne_ccor_favorecido::text) as ne_ccor_favorecido, + ne_ccor_favorecido_descricao::text as ne_ccor_favorecido_descricao, + ne_ccor_ano_emissao::integer as ne_ccor_ano_emissao, + ptres::text as ptres, + fonte_recursos_detalhada::text as fonte_recursos_detalhada, + fonte_recursos_detalhada_descricao::text as fonte_recursos_detalhada_descricao, + {{ parse_financial_value("despesas_empenhadas") }} as despesas_empenhadas, + {{ parse_financial_value("despesas_liquidadas") }} as despesas_liquidadas, + {{ parse_financial_value("despesas_pagas") }} as despesas_pagas, + {{ parse_financial_value("restos_a_pagar_inscritos") }} as restos_a_pagar_inscritos, + {{ parse_financial_value("restos_a_pagar_pagos") }} as restos_a_pagar_pagos, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("siafi", "empenhos_tesouro_parlamentares") }} + where ne_ccor_ano_emissao ~ '^[0-9]{4}$' + ) + +select * +from empenhos_teds_raw \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/schema.yaml b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/schema.yaml new file mode 100644 index 00000000..ca6fa6a5 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/schema.yaml @@ -0,0 +1,102 @@ +version: 2 + +models: + - name: empenhos_tesouro_ted + description: > + Tabela da camada bronze contendo informações detalhadas sobre os empenhos do Tesouro Gerencial + vinculados a emendas parlamentares e TEDs. + A tabela realiza a limpeza do número do processo, padronização de nomes de favorecidos + e conversão de valores financeiros de string para numeric(15,2) via macro. + meta: + tags: + - bronze + columns: + - name: programa_governo + description: > + Código identificador do programa de governo associado ao empenho. + - name: programa_governo_descricao + description: > + Descrição textual do programa de governo. + - name: acao_governo + description: > + Código da ação orçamentária (Ação de Governo). + - name: acao_governo_descricao + description: > + Descrição da ação orçamentária. + - name: emissao_mes + description: > + Mês de emissão da nota de empenho. + - name: emissao_dia + description: > + Dia específico da emissão da nota de empenho. + - name: ne_ccor + description: > + Número completo da nota de empenho (Chave natural principal). + - name: ne_num_processo + description: > + Número do processo administrativo, limpo de caracteres especiais (pontos, barras, hifens) via regex. + - name: ne_info_complementar + description: > + Informações adicionais registradas na nota de empenho. + - name: ne_ccor_descricao + description: > + Descrição detalhada do item ou serviço empenhado. + - name: doc_observacao + description: > + Observações registradas no documento fiscal ou contábil. + - name: natureza_despesa + description: > + Código da natureza da despesa (ex: 339030). + - name: natureza_despesa_descricao + description: > + Descrição da categoria da natureza de despesa. + - name: ne_ccor_favorecido + description: > + CNPJ ou CPF do favorecido do empenho, convertido para caixa alta. + - name: ne_ccor_favorecido_descricao + description: > + Nome ou Razão Social do favorecido. + - name: ne_ccor_ano_emissao + description: > + Ano de emissão do empenho (YYYY), validado via regex para garantir 4 dígitos numéricos. + - name: ptres + description: > + Programa de Trabalho Resumido (PTRES). + - name: fonte_recursos_detalhada + description: > + Código detalhado da fonte de financiamento. + - name: fonte_recursos_detalhada_descricao + description: > + Descrição da fonte de recursos. + - name: despesas_empenhadas + description: > + Valor total das despesas empenhadas, convertido para numeric(15,2). + - name: despesas_liquidadas + description: > + Valor das despesas que já passaram pela liquidação, convertido para numeric(15,2). + - name: despesas_pagas + description: > + Valor efetivamente pago ao favorecido, convertido para numeric(15,2). + - name: restos_a_pagar_inscritos + description: > + Valor inscrito em Restos a Pagar (RP), convertido para numeric(15,2). + - name: restos_a_pagar_pagos + description: > + Valor de Restos a Pagar que já foram quitados, convertido para numeric(15,2). + - name: dt_ingest + description: > + Timestamp da ingestão dos dados na camada raw, ajustado para o fuso horário de Brasília (UTC-3). + + tests: + - verificacao_tipagem: + nome_tabela: 'mir.empenhos_tesouro_parlamentares' + nome_coluna: 'ne_ccor_ano_emissao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'mir.empenhos_tesouro_parlamentares' + nome_coluna: 'despesas_empenhadas' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'mir.empenhos_tesouro_parlamentares' + nome_coluna: 'restos_a_pagar_inscritos' + tipo_esperado: 'numeric' \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/sources.yml b/airflow_lappis/dags/dbt/mir/models/sources.yml index 71e1d4fc..3d34b609 100644 --- a/airflow_lappis/dags/dbt/mir/models/sources.yml +++ b/airflow_lappis/dags/dbt/mir/models/sources.yml @@ -26,3 +26,8 @@ sources: schema: senado_federal tables: - name: senadores + + - name: siafi + schema: siafi + tables: + - name: empenhos_tesouro_parlamentares \ No newline at end of file From 9427e2b35c9c324c3b2ea4653a969a3d1d0d0131 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Wed, 18 Mar 2026 22:15:57 -0300 Subject: [PATCH 222/317] feat: silver planos cruzada com parlamentares --- .../emendas_dbt/silver/planos_partidos.sql | 96 ++++++++++ .../mir/models/emendas_dbt/silver/schema.yml | 168 ++++++++++++++++++ 2 files changed, 264 insertions(+) create mode 100644 airflow_lappis/dags/dbt/mir/models/emendas_dbt/silver/planos_partidos.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/emendas_dbt/silver/schema.yml diff --git a/airflow_lappis/dags/dbt/mir/models/emendas_dbt/silver/planos_partidos.sql b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/silver/planos_partidos.sql new file mode 100644 index 00000000..c08d2580 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/silver/planos_partidos.sql @@ -0,0 +1,96 @@ +{{ config(materialized='table') }} + +WITH bronze_planos_acoes AS ( + SELECT * FROM {{ ref('planos_acoes') }} +), + +bronze_deputados AS ( + SELECT * FROM {{ ref('deputados') }} +), + +bronze_senadores AS ( + SELECT * FROM {{ ref('senadores') }} +), + +parlamentares_unificados AS ( + SELECT + id AS id_parlamentar, + TRIM(UPPER(nome)) AS chave_join_nome, + nome AS nome_parlamentar, + 'Deputado' AS cargo_parlamentar, + siglapartido AS sigla_partido, + siglauf AS uf_parlamentar, + urlfoto AS url_foto, + email + FROM bronze_deputados + + UNION ALL + + SELECT + id AS id_parlamentar, + TRIM(UPPER(nome_parlamentar)) AS chave_join_nome, + nome_parlamentar AS nome_parlamentar, + 'Senador' AS cargo_parlamentar, + sigla_partido AS sigla_partido, + uf AS uf_parlamentar, + url_foto AS url_foto, + email + FROM bronze_senadores +), + +planos_acoes_tratado AS ( + SELECT + *, + TRIM(UPPER(nome_parlamentar_emenda_plano_acao)) AS chave_join_nome + FROM bronze_planos_acoes +), + +final AS ( + SELECT + -- Todas as features de Planos de Ação + pa.id_plano_acao, + pa.codigo_plano_acao, + pa.ano_plano_acao, + pa.modalidade_plano_acao, + pa.situacao_plano_acao, + pa.cnpj_beneficiario_plano_acao, + pa.nome_beneficiario_plano_acao, + pa.uf_beneficiario_plano_acao, + pa.codigo_banco_plano_acao, + pa.codigo_situacao_dado_bancario_plano_acao, + pa.nome_banco_plano_acao, + pa.numero_agencia_plano_acao, + pa.dv_agencia_plano_acao, + pa.numero_conta_plano_acao, + pa.dv_conta_plano_acao, + pa.nome_parlamentar_emenda_plano_acao, + pa.ano_emenda_parlamentar_plano_acao, + pa.codigo_parlamentar_emenda_plano_acao, + pa.sequencial_emenda_parlamentar_plano_acao, + pa.numero_emenda_parlamentar_plano_acao, + pa.codigo_emenda_parlamentar_formatado_plano_acao, + pa.codigo_descricao_areas_politicas_publicas_plano_acao, + pa.descricao_programacao_orcamentaria_plano_acao, + pa.motivo_impedimento_plano_acao, + pa.valor_custeio_plano_acao, + pa.valor_investimento_plano_acao, + pa.id_programa, + + -- Features unificadas dos Parlamentares + parl.id_parlamentar, + parl.cargo_parlamentar, + parl.nome_parlamentar, + parl.sigla_partido, + parl.uf_parlamentar, + parl.url_foto, + parl.email, + + -- Data de ingestão (mantendo a da tabela fato) + pa.dt_ingest + + FROM planos_acoes_tratado pa + LEFT JOIN parlamentares_unificados parl + ON pa.chave_join_nome = parl.chave_join_nome +) + +SELECT * FROM final \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/emendas_dbt/silver/schema.yml b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/silver/schema.yml new file mode 100644 index 00000000..4500ba9f --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/silver/schema.yml @@ -0,0 +1,168 @@ +version: 2 + +models: + + - name: planos_partidos + description: > + Tabela silver que integra as informações dos planos de ação especiais relacionados a emendas + parlamentares do TransfereGov com metadados de parlamentares (deputados e senadores). + A partir da tabela bronze planos_acoes e das bases de deputados e senadores, enriquece cada + plano de ação com dados de identificação do parlamentar, partido, UF, foto e e-mail. + meta: + tags: + - silver + columns: + # Colunas de planos de ação (herdadas da camada bronze) + - name: id_plano_acao + description: > + Identificador único do Plano de Ação (PA). + - name: codigo_plano_acao + description: > + Código do Programa concatenado com o ID do Plano de Ação. + - name: ano_plano_acao + description: > + Ano de criação do Plano de Ação. + - name: modalidade_plano_acao + description: > + Modalidade de Transferência do Plano de Ação. + - name: situacao_plano_acao + description: > + Situação do Plano de Ação. + - name: cnpj_beneficiario_plano_acao + description: > + CNPJ – Cadastro Nacional de Pessoa Jurídica do Beneficiário do Plano de Ação. + - name: nome_beneficiario_plano_acao + description: > + Nome do Beneficiário do Plano de Ação. + - name: uf_beneficiario_plano_acao + description: > + Sigla da Unidade de Federação do beneficiário. + - name: codigo_banco_plano_acao + description: > + Código do Banco do Plano de Ação. + - name: codigo_situacao_dado_bancario_plano_acao + description: > + Código da Situação da Conta Corrente do Plano de Ação. + - name: nome_banco_plano_acao + description: > + Nome do Banco do Plano de Ação. + - name: numero_agencia_plano_acao + description: > + Número da Agência Bancária da Conta Corrente do Plano de Ação. + - name: dv_agencia_plano_acao + description: > + Dígito Verificador da Agência Bancária da Conta Corrente do Plano de Ação. + - name: numero_conta_plano_acao + description: > + Número da Conta Corrente do Plano de Ação. + - name: dv_conta_plano_acao + description: > + Dígito Verificador da Conta Corrente do Plano de Ação. + - name: nome_parlamentar_emenda_plano_acao + description: > + Nome do Parlamentar Autor da Emenda vinculado ao Plano de Ação. + - name: ano_emenda_parlamentar_plano_acao + description: > + Ano da Emenda Parlamentar associada ao Plano de Ação. + - name: codigo_parlamentar_emenda_plano_acao + description: > + Código do Parlamentar Autor da Emenda. + - name: sequencial_emenda_parlamentar_plano_acao + description: > + Sequencial da Emenda por Parlamentar no Ano. + - name: numero_emenda_parlamentar_plano_acao + description: > + Concatenação do Ano, Código e Sequencial do Parlamentar, formando o identificador da emenda. + - name: codigo_emenda_parlamentar_formatado_plano_acao + description: > + Código formatado da Emenda Parlamentar associada ao Plano de Ação. + - name: codigo_descricao_areas_politicas_publicas_plano_acao + description: > + Concatenação dos códigos e descrições dos tipos das áreas de políticas públicas + com os códigos e descrições das áreas das políticas públicas. + - name: descricao_programacao_orcamentaria_plano_acao + description: > + Concatenação das programações orçamentárias constantes da Lei Orçamentária do ente + beneficiado na qual o recurso será apropriado. + - name: motivo_impedimento_plano_acao + description: > + Motivo do impedimento do Plano de Ação, quando existente. + - name: valor_custeio_plano_acao + description: > + Valor consolidado de custeio das emendas parlamentares do Plano de Ação. + - name: valor_investimento_plano_acao + description: > + Valor consolidado de investimento das emendas parlamentares do Plano de Ação. + - name: id_programa + description: > + Identificador único do Programa associado ao Plano de Ação. + # Colunas de metadados dos parlamentares + - name: id_parlamentar + description: > + Identificador único do parlamentar na base unificada de deputados e senadores. + - name: cargo_parlamentar + description: > + Cargo do parlamentar responsável pela emenda (por exemplo, Deputado ou Senador). + - name: nome_parlamentar + description: > + Nome do parlamentar conforme registrado na base de origem (Câmara dos Deputados ou Senado Federal). + - name: sigla_partido + description: > + Sigla do partido ao qual o parlamentar está filiado. + - name: uf_parlamentar + description: > + Sigla da Unidade Federativa (UF) que o parlamentar representa. + - name: url_foto + description: > + URL da foto oficial do parlamentar na base de origem. + - name: email + description: > + Endereço de e-mail institucional do parlamentar. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. + tests: + - verificacao_tipagem: + nome_tabela: 'emendas.planos_partidos' + nome_coluna: 'id_plano_acao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.planos_partidos' + nome_coluna: 'ano_plano_acao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.planos_partidos' + nome_coluna: 'codigo_situacao_dado_bancario_plano_acao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.planos_partidos' + nome_coluna: 'numero_agencia_plano_acao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.planos_partidos' + nome_coluna: 'numero_conta_plano_acao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.planos_partidos' + nome_coluna: 'sequencial_emenda_parlamentar_plano_acao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.planos_partidos' + nome_coluna: 'valor_custeio_plano_acao' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.planos_partidos' + nome_coluna: 'valor_investimento_plano_acao' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.planos_partidos' + nome_coluna: 'id_programa' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.planos_partidos' + nome_coluna: 'id_parlamentar' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.planos_partidos' + nome_coluna: 'dt_ingest' + tipo_esperado: 'timestamp with time zone' \ No newline at end of file From 1ca0c3c1c7df558e32ab75f166f117ec54fbae86 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Fri, 20 Mar 2026 15:16:03 -0300 Subject: [PATCH 223/317] feat(dag): siafi email empenhos emendas parlamentares --- .../empenhos_tesouro_emendas_ingest_dag.py | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_emendas_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_emendas_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_emendas_ingest_dag.py new file mode 100644 index 00000000..f6f220c3 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_emendas_ingest_dag.py @@ -0,0 +1,171 @@ +from typing import Dict, Any, Optional +from airflow import DAG +from airflow.operators.python import PythonOperator +from airflow.models import Variable +from datetime import datetime, timedelta +import logging +import json +from schedule_loader import get_dynamic_schedule +from cliente_email import fetch_and_process_email +from cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn +import pandas as pd +import io + +# Configurações básicas da DAG +default_args = { + "owner": "Tiago", + "depends_on_past": False, + "retries": 1, + "retry_delay": timedelta(minutes=5), +} + +COLUMN_MAPPING = { + 0: "emissao_mes", + 1: "emissao_dia", + 2: "programa_governo", + 3: "programa_governo_descricao", + 4: "acao_governo", + 5: "acao_governo_descricao", + 6: "autor_emendas_orcamento", + 7: "autor_emendas_orcamento_descricao", + 8: "uf_pt", + 9: "uf_pt_descricao", + 10: "municipio_pt", + 11: "ne_ccor", + 12: "ne_num_processo", + 13: "ne_info_complementar", + 14: "ne_ccor_descricao", + 15: "doc_observacao", + 16: "grupo_despesa", + 17: "grupo_despesa_descricao", + 18: "natureza_despesa", + 19: "natureza_despesa_descricao", + 20: "modalidade_aplicacao", + 21: "modalidade_aplicacao_descricao", + 22: "ne_ccor_favorecido", + 23: "ne_ccor_favorecido_descricao", + 24: "ne_ccor_ano_emissao", + 25: "ptres", + 26: "item_informacao", + 27: "item_informacao_descricao", + 28: "despesas_empenhadas", + 29: "despesas_liquidadas", + 30: "despesas_pagas", + 31: "restos_a_pagar_inscritos", + 32: "restos_a_pagar_pagos" +} + +EMAIL_SUBJECT = "notas_de_empenhos_emendas_parlamentares" +SKIPROWS = 12 + +# Configurações da DAG +with DAG( + dag_id="email_tesouro_emendas_ingest", + default_args=default_args, + description="Processa anexos dos empenhos vindo do email, formata e insere no db", + schedule_interval=get_dynamic_schedule("empenhos_tesouro_emendas_ingest_dag"), + start_date=datetime(2023, 12, 1), + catchup=False, + tags=["MIR", "email", "empenhos", "tesouro", "emendas"], +) as dag: + + def process_email_data(**context: Dict[str, Any]) -> Optional[Any]: + creds = json.loads(Variable.get("email_credentials")) + + EMAIL = creds["email"] + PASSWORD = creds["password"] + IMAP_SERVER = creds["imap_ser" \ + "ver"] + SENDER_EMAIL = creds["sender_email"] + + try: + logging.info("Iniciando o processamento dos emails...") + csv_data = fetch_and_process_email( + IMAP_SERVER, + EMAIL, + PASSWORD, + SENDER_EMAIL, + EMAIL_SUBJECT, + COLUMN_MAPPING, + skiprows=SKIPROWS, + ) + if not csv_data: + logging.warning( + "Nenhum CSV valido foi extraido dos e-mails encontrados " + "para o assunto esperado." + ) + return None + + logging.info( + "CSV processado com sucesso. Dados encontrados: %s", len(csv_data) + ) + return csv_data + except Exception as e: + logging.error("Erro no processamento dos emails: %s", str(e)) + raise + + def insert_data_to_db(**context: Dict[str, Any]) -> None: + """ + Função para inserir os dados no banco de dados. + Os dados do CSV são recuperados do XCom. + """ + try: + task_instance: Any = context["ti"] + csv_data: Any = task_instance.xcom_pull(task_ids="process_emails") + + if not csv_data: + logging.warning("Nenhum dado para inserir no banco.") + return + + df = pd.read_csv(io.StringIO(csv_data)) + df = df[df["ne_ccor_ano_emissao"].astype(str).str.startswith("20")] + data = df.to_dict(orient="records") + + # Adicionar dt_ingest a cada registro + for record in data: + record["dt_ingest"] = datetime.now().isoformat() + + postgres_conn_str = get_postgres_conn("postgres_mir") + db = ClientPostgresDB(postgres_conn_str) + + unique_key = [ + "ne_ccor", + "natureza_despesa", + "doc_observacao", + "ne_ccor_ano_emissao", + "emissao_dia", + "emissao_mes", + "despesas_empenhadas", + "despesas_liquidadas", + "despesas_pagas", + ] + + db.insert_data( + data, + "empenhos_tesouro_emendas_parlamentares", + conflict_fields=unique_key, + primary_key=unique_key, + schema="siafi", + ) + logging.info("Dados inseridos com sucesso no banco de dados.") + except Exception as e: + logging.error("Erro ao inserir dados no banco: %s", str(e)) + raise + + # Tarefa 1: Processar os e-mails e retornar CSV + process_emails_task = PythonOperator( + task_id="process_emails", + python_callable=process_email_data, + provide_context=True, + ) + + # Tarefa 2: Inserir os dados no banco de dados + insert_to_db_task = PythonOperator( + task_id="insert_to_db", + python_callable=insert_data_to_db, + provide_context=True, + ) + + # Fluxo da DAG + process_emails_task >> insert_to_db_task From b75cffe3fec04ccce1033c1819130b8e2f7320c4 Mon Sep 17 00:00:00 2001 From: Lucas Bottino Date: Wed, 25 Mar 2026 13:31:36 -0300 Subject: [PATCH 224/317] feat/dag: adiciona dag para consumo de notas de empenho de parlamentares via email --- ...mpenho_emendas_parlamentares_ingest_dag.py | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/tesouro_gerencial/mcid/empenho_emendas_parlamentares_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mcid/empenho_emendas_parlamentares_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mcid/empenho_emendas_parlamentares_ingest_dag.py new file mode 100644 index 00000000..c2cb5789 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mcid/empenho_emendas_parlamentares_ingest_dag.py @@ -0,0 +1,156 @@ +import io +import json +import logging +from datetime import datetime, timedelta +from typing import Any, Dict, Optional + +import pandas as pd +from airflow import DAG +from airflow.exceptions import AirflowSkipException +from airflow.models import Variable +from airflow.operators.python import PythonOperator +from cliente_email import fetch_and_process_email +from cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn +from schedule_loader import get_dynamic_schedule + +default_args = { + "owner": "Lucas", + "depends_on_past": False, + "retries": 1, + "retry_delay": timedelta(minutes=5), +} + +COLUMN_MAPPING = { + 0: "emissao_mes", + 1: "emissao_dia", + 2: "programa_governo_numero", + 3: "programa_governo_nome", + 4: "acao_governo_codigo", + 5: "acao_governo_nome", + 6: "autor_emendas_orcamento_codigo", + 7: "autor_emendas_orcamento_nome", + 8: "uf_codigo", + 9: "uf_nome", + 10: "municipio", + 11: "ne_ccor", + 12: "ne_num_processo", + 13: "ne_info_complementar", + 14: "ne_ccor_descricao", + 15: "doc_observacao", + 16: "grupo_despesa_codigo", + 17: "grupo_despesa_nome", + 18: "natureza_despesa_codigo", + 19: "natureza_despesa_nome", + 20: "modalidade_aplicacao_codigo", + 21: "modalidade_aplicacao_nome", + 22: "ne_ccor_favorecido_codigo", + 23: "ne_ccor_favorecido_nome", + 24: "ne_ccor_ano_emissao", + 25: "ptres", + 26: "fonte_recursos_detalhada", + 27: "fonte_recursos_detalhada_descricao", + 28: "restos_a_pagar_inscritos", + 29: "restos_a_pagar_pagos", +} + +EMAIL_SUBJECT = "notas_empenho_emendas_parlamentares_mcid" +SKIPROWS = 9 + +with DAG( + dag_id="empenho_emendas_parlamentares_ingest_dag", + default_args=default_args, + description="Processa e ingere dados de empenho de emendas parlamentares do MCID", + schedule_interval=get_dynamic_schedule("empenho_emendas_parlamentares_ingest_dag"), + start_date=datetime(2026, 3, 25), + catchup=False, + tags=["email", "empenhos", "tesouro", "emendas"], +) as dag: + + def process_email_data(**context: Dict[str, Any]) -> Optional[Any]: + creds = json.loads(Variable.get("email_credentials")) + + EMAIL = creds["email"] + PASSWORD = creds["password"] + IMAP_SERVER = creds["imap_server"] + SENDER_EMAIL = creds["sender_email"] + + try: + logging.info("Iniciando o processamento dos emails") + csv_data = fetch_and_process_email( + IMAP_SERVER, + EMAIL, + PASSWORD, + SENDER_EMAIL, + EMAIL_SUBJECT, + COLUMN_MAPPING, + skiprows=SKIPROWS, + ) + if not csv_data: + logging.warning("Nenhum e-mail encontrado com o assunto esperado.") + raise AirflowSkipException("Nenhum e-mail encontrado. Task ignorada.") + + logging.info( + "CSV processado com sucesso. Dados encontrados: %s", len(csv_data) + ) + return csv_data + except Exception as e: + logging.error("Erro no processamento dos emails: %s", str(e)) + raise + + def insert_data_to_db(**context: Dict[str, Any]) -> None: + try: + task_instance: Any = context["ti"] + csv_data: Any = task_instance.xcom_pull(task_ids="process_emails") + + if not csv_data: + logging.warning("Nenhum dado para inserir no banco.") + raise AirflowSkipException( + "Nenhum dado foi encontrado para inserção no BD" + ) + return + + df = pd.read_csv(io.StringIO(csv_data), skiprows=[1, 2, 3]) + df = df[df["ne_ccor_ano_emissao"].astype(str).str.startswith("20")] + data = df.to_dict(orient="records") + + for record in data: + record["dt_ingest"] = datetime.now().isoformat() + + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + unique_key = [ + "ne_ccor", + "emissao_dia", + "emissao_mes", + "programa_governo_numero", + "programa_governo_nome", + "ne_num_processo", + ] + + db.insert_data( + data, + "empenho_emendas_parlamentares", + conflict_fields=unique_key, + primary_key=unique_key, + schema="siafi", + ) + logging.info("Dados inseridos com sucesso no banco de dados.") + except Exception as e: + logging.error("Erro ao inserir dados no banco: %s", str(e)) + raise + + process_emails_task = PythonOperator( + task_id="process_emails", + python_callable=process_email_data, + provide_context=True, + ) + + insert_to_db_task = PythonOperator( + task_id="insert_to_db", + python_callable=insert_data_to_db, + provide_context=True, + ) + + process_emails_task >> insert_to_db_task From db2a49894b7c696f0a3ed33993dd324a3ae2af3f Mon Sep 17 00:00:00 2001 From: Lucas Bottino Date: Fri, 27 Mar 2026 10:51:19 -0300 Subject: [PATCH 225/317] =?UTF-8?q?feat/dag:=20adiciona=20dag=20para=20con?= =?UTF-8?q?sumo=20de=20or=C3=A7amento=20por=20a=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../orcamento_mcid_por_acao_ingest_dag.py | 214 ++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/tesouro_gerencial/mcid/orcamento_mcid_por_acao_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mcid/orcamento_mcid_por_acao_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mcid/orcamento_mcid_por_acao_ingest_dag.py new file mode 100644 index 00000000..c1f93682 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mcid/orcamento_mcid_por_acao_ingest_dag.py @@ -0,0 +1,214 @@ +import io +import json +import logging +from datetime import datetime, timedelta +from typing import Any, Dict, List, Optional + +import cliente_email # importar o módulo, não só a função +import pandas as pd +from airflow import DAG +from airflow.exceptions import AirflowSkipException +from airflow.models import Variable +from airflow.operators.python import PythonOperator +from cliente_email import fetch_and_process_email +from cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn +from schedule_loader import get_dynamic_schedule + +default_args = { + "owner": "Lucas", + "depends_on_past": False, + "retries": 1, + "retry_delay": timedelta(minutes=5), +} + +COLUMN_MAPPING = { + 0: "acao_governo_codigo", + 1: "acao_governo_nome", + 2: "programa_governo_codigo", + 3: "programa_governo_nome", + 4: "ne_ccor", + 5: "ne_ccor_favorecido_codigo", + 6: "ne_ccor_favorecido_nome", + 7: "favorecido_cep", + 8: "favorecido_municipio_codigo", + 9: "favorecido_municipio_nome", + 10: "favorecido_regiao", + 11: "favorecido_ug_uf_codigo", + 12: "favorecido_ug_uf_nome", + 13: "fonte_recursos_detalhada", + 14: "fonte_recursos_detalhada_descricao", + 15: "pt", + 16: "ptres", + 17: "plano_orcamentario_ug_executora_codigo", + 18: "plano_orcamentario_cod1", + 19: "plano_orcamentario_cod2", + 20: "plano_orcamentario_programa", + 21: "plano_orcamentario_acao_orcamentaria", + 22: "plano_orcamentario_medida", + 23: "plano_orcamentario_descricao", + 24: "ug_executora_codigo", + 25: "ug_executora_nome", + 26: "ug_responsavel_codigo", + 27: "ug_responsavel_nome", + 28: "pl_codigo", + 29: "pl_nome", + 30: "natureza_despesa_codigo", + 31: "natureza_despesa_nome", + 32: "dotacao_inicial", + 33: "dotacao_atualizada", + 34: "despesas_empenhadas", + 35: "despesas_empenhadas_a_liquidar", + 36: "despesas_liquidadas_a_pagar", + 37: "despesas_pagas", +} + +EMAIL_SUBJECT = "orcamento_mcid_por_acao" +SKIPROWS = 10 + + +# A formatação do CSV estava como utf-16. +# Função criada para consumo sem erro de formatação +def _patched_format_csv( + csv_data: str, + column_mapping: Optional[Dict[int, str]], + skiprows: int, +) -> pd.DataFrame: + """Substitui o format_csv do cliente_email com suporte a UTF-16 e TSV.""" + # Decodifica UTF-16 se ainda vier como bytes + if isinstance(csv_data, bytes): + csv_data = csv_data.decode("utf-16") + + if column_mapping: + df = pd.read_csv( + io.StringIO(csv_data), + skiprows=skiprows, + header=None, + sep="\t", + engine="python", + on_bad_lines="skip", + ) + column_names: List[str] = [ + column_mapping.get(i, f"col_{i}") for i in range(len(df.columns)) + ] + df.columns = pd.Index(column_names) + else: + df = pd.read_csv( + io.StringIO(csv_data), + skiprows=skiprows, + header=0, + sep="\t", + engine="python", + on_bad_lines="skip", + ) + return df + + +with DAG( + dag_id="orcamento_mcid_por_acao_ingest_dag", + default_args=default_args, + description="Processa e ingere dados de orcamento por acao do MCID do Tesouro", + schedule_interval=get_dynamic_schedule("orcamento_mcid_por_acao_ingest_dag"), + start_date=datetime(2026, 3, 23), + catchup=False, + tags=["email", "orcamento", "tesouro", "mcid"], +) as dag: + + def process_email_data(**context: Dict[str, Any]) -> Optional[Any]: + creds = json.loads(Variable.get("email_credentials")) + + EMAIL = creds["email"] + PASSWORD = creds["password"] + IMAP_SERVER = creds["imap_server"] + SENDER_EMAIL = creds["sender_email"] + + # Monkey-patch: substitui format_csv do cliente_email pela versão corrigida + cliente_email.format_csv = _patched_format_csv + + try: + logging.info("Iniciando o processamento dos emails") + csv_data = fetch_and_process_email( + IMAP_SERVER, + EMAIL, + PASSWORD, + SENDER_EMAIL, + EMAIL_SUBJECT, + COLUMN_MAPPING, + skiprows=SKIPROWS, + ) + if not csv_data: + logging.warning("Nenhum e-mail encontrado com o assunto esperado.") + raise AirflowSkipException("Nenhum e-mail encontrado. Task ignorada.") + + logging.info( + "CSV processado com sucesso. Registros encontrados: %s", len(csv_data) + ) + return csv_data + except Exception as e: + logging.error("Erro no processamento dos emails: %s", str(e)) + raise + + def insert_data_to_db(**context: Dict[str, Any]) -> None: + try: + task_instance: Any = context["ti"] + csv_data: Any = task_instance.xcom_pull(task_ids="process_emails") + + if not csv_data: + logging.warning("Nenhum dado para inserir no banco.") + raise AirflowSkipException( + "Nenhum dado foi encontrado para inserção no BD" + ) + + df = pd.read_csv(io.StringIO(csv_data)) + data = df.to_dict(orient="records") + + for record in data: + record["dt_ingest"] = datetime.now().isoformat() + + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + unique_key = [ + "acao_governo_codigo", + "ne_ccor", + "programa_governo_codigo", + "pt", + "ptres", + "pl_codigo", + "ug_executora_codigo", + "natureza_despesa_codigo", + "fonte_recursos_detalhada", + "plano_orcamentario_ug_executora_codigo", + "plano_orcamentario_cod1", + "plano_orcamentario_cod2", + "plano_orcamentario_programa", + "plano_orcamentario_acao_orcamentaria", + "plano_orcamentario_medida", + "plano_orcamentario_descricao", + ] + + db.insert_data( + data, + "orcamento_mcid_por_acao", + # conflict_fields=unique_key, + # primary_key=unique_key, + schema="siafi", + ) + logging.info("Dados inseridos com sucesso no banco de dados.") + except Exception as e: + logging.error("Erro ao inserir dados no banco: %s", str(e)) + raise + + process_emails_task = PythonOperator( + task_id="process_emails", + python_callable=process_email_data, + provide_context=True, + ) + + insert_to_db_task = PythonOperator( + task_id="insert_to_db", + python_callable=insert_data_to_db, + provide_context=True, + ) + + process_emails_task >> insert_to_db_task From 3dea3c4ceac599e86725d6756f04a100e97e62d8 Mon Sep 17 00:00:00 2001 From: Lucas Bottino Date: Fri, 27 Mar 2026 11:38:10 -0300 Subject: [PATCH 226/317] feat/dag: adiciona dag para consumo as dotacoes e execucoes de outras fontes --- ..._execucao_outras_fontes_mcid_ingest_dag.py | 193 ++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/tesouro_gerencial/mcid/dotacao_execucao_outras_fontes_mcid_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mcid/dotacao_execucao_outras_fontes_mcid_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mcid/dotacao_execucao_outras_fontes_mcid_ingest_dag.py new file mode 100644 index 00000000..4e4636bf --- /dev/null +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mcid/dotacao_execucao_outras_fontes_mcid_ingest_dag.py @@ -0,0 +1,193 @@ +import io +import json +import logging +from datetime import datetime, timedelta +from typing import Any, Dict, List, Optional + +import cliente_email +import pandas as pd +from airflow import DAG +from airflow.exceptions import AirflowSkipException +from airflow.models import Variable +from airflow.operators.python import PythonOperator +from cliente_email import fetch_and_process_email +from cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn +from schedule_loader import get_dynamic_schedule + +default_args = { + "owner": "Lucas", + "depends_on_past": False, + "retries": 1, + "retry_delay": timedelta(minutes=5), +} + +COLUMN_MAPPING = { + 0: "unidade_orcamentaria_codigo", + 1: "unidade_orcamentaria_nome", + 2: "acao_governo_codigo", + 3: "acao_governo_nome", + 4: "programa_governo_codigo", + 5: "programa_governo_nome", + 6: "plano_orcamentario_codigo", + 7: "plano_orcamentario_funcao", + 8: "plano_orcamentario_subfuncao", + 9: "plano_orcamentario_programa", + 10: "plano_orcamentario_acao", + 11: "plano_orcamentario_medida", + 12: "plano_orcamentario_descricao", + 13: "elemento_despesa_codigo", + 14: "elemento_despesa_nome", + 15: "orgao_uge_codigo", + 16: "orgao_uge_nome", + 17: "uge_matriz_filial", + 18: "ug_executora_codigo", + 19: "ug_executora_nome", + 20: "fixacao_despesa_loa", + 21: "dotacao_inicial", + 22: "dotacao_atualizada", + 23: "credito_disponivel", + 24: "despesas_empenhadas", + 25: "despesas_empenhadas_a_liquidar", + 26: "despesas_liquidadas_a_pagar", + 27: "despesas_pagas", + 28: "restos_a_pagar_inscritos", + 29: "restos_a_pagar_pagos", +} + +EMAIL_SUBJECT = "dotacao_execucao_outras_fontes_mcid" +SKIPROWS = 12 + + +def _patched_format_csv( + csv_data: str, + column_mapping: Optional[Dict[int, str]], + skiprows: int, +) -> pd.DataFrame: + """Substitui o format_csv do cliente_email com suporte a UTF-16 e TSV.""" + # Decodifica UTF-16 se ainda vier como bytes + if isinstance(csv_data, bytes): + csv_data = csv_data.decode("utf-16") + + if column_mapping: + df = pd.read_csv( + io.StringIO(csv_data), + skiprows=skiprows, + header=None, + sep="\t", + engine="python", + on_bad_lines="skip", + ) + column_names: List[str] = [ + column_mapping.get(i, f"col_{i}") for i in range(len(df.columns)) + ] + df.columns = pd.Index(column_names) + else: + df = pd.read_csv( + io.StringIO(csv_data), + skiprows=skiprows, + header=0, + sep="\t", + engine="python", + on_bad_lines="skip", + ) + return df + + +with DAG( + dag_id="dotacao_execucao_outras_fontes_mcid_ingest_dag", + default_args=default_args, + description="Processa e ingere dados de execução de outras fontes de MCID", + schedule_interval=get_dynamic_schedule("dotacao_execucao_outras_fontes_mcid"), + catchup=False, + start_date=datetime(2026, 3, 27), + tags=["email", "mcid", "tesouro", "dotacao", "execucao"], +) as dag: + + def process_email_data(**context: Dict[str, Any]) -> Optional[Any]: + creds = json.loads(Variable.get("email_credentials")) + + EMAIL = creds["email"] + PASSWORD = creds["password"] + IMAP_SERVER = creds["imap_server"] + SENDER_EMAIL = creds["sender_email"] + + cliente_email.format_csv = _patched_format_csv + + try: + logging.info("Iniciando o processamento dos emails") + csv_data = fetch_and_process_email( + IMAP_SERVER, + EMAIL, + PASSWORD, + SENDER_EMAIL, + EMAIL_SUBJECT, + COLUMN_MAPPING, + skiprows=SKIPROWS, + ) + if not csv_data: + logging.warning("Nenhum e-mail encontrado com o assunto esperado.") + raise AirflowSkipException("Nenhum e-mail encontrado. Task ignorada.") + + logging.info( + "CSV processado com sucesso. Registros encontrados: %s", len(csv_data) + ) + return csv_data + except Exception as e: + logging.error("Erro no processamento dos emails: %s", str(e)) + raise + + def insert_data_to_db(**context: Dict[str, Any]) -> None: + try: + task_instance: Any = context["ti"] + csv_data: Any = task_instance.xcom_pull(task_ids="process_emails") + + if not csv_data: + logging.warning("Nenhum dado para inserir no banco.") + raise AirflowSkipException( + "Nenhum dado foi encontrado para inserção no BD" + ) + + df = pd.read_csv(io.StringIO(csv_data)) + data = df.to_dict(orient="records") + + for record in data: + record["dt_ingest"] = datetime.now().isoformat() + + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + unique_key = [ + "unidade_orcamentaria_codigo", + "acao_governo_codigo", + "plano_orcamentario_codigo", + "plano_orcamentario_descricao", + "elemento_despesa_codigo", + "ug_executora_codigo", + ] + + db.insert_data( + data, + "dotacao_execucao_outras_fontes_mcid", + # conflict_fields=unique_key, + # primary_key=unique_key, + schema="siafi", + ) + logging.info("Dados inseridos com sucesso no banco de dados.") + except Exception as e: + logging.error("Erro ao inserir dados no banco: %s", str(e)) + raise + + process_emails_task = PythonOperator( + task_id="process_emails", + python_callable=process_email_data, + provide_context=True, + ) + + insert_to_db_task = PythonOperator( + task_id="insert_to_db", + python_callable=insert_data_to_db, + provide_context=True, + ) + + process_emails_task >> insert_to_db_task From fb0ec561112e5375b18156875e7688ccda95a7e6 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Fri, 27 Mar 2026 23:19:42 -0300 Subject: [PATCH 227/317] feat(bronze): emendas tesouro gerencial --- .../mir/models/emendas_dbt/bronze/schema.yml | 120 ++++++++++++++++++ .../models/emendas_dbt/bronze/tg_emendas.sql | 53 ++++++++ .../dags/dbt/mir/models/sources.yml | 3 +- 3 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/tg_emendas.sql diff --git a/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/schema.yml b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/schema.yml index fc46cbc6..7468e6c8 100644 --- a/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/schema.yml +++ b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/schema.yml @@ -1010,3 +1010,123 @@ models: nome_tabela: 'emendas.finalidade_especial' nome_coluna: 'cd_area_politica_publica_pt' tipo_esperado: 'integer' + + - name: tg_emendas + description: > + Tabela com informações de execução orçamentária do SIAFI para empenhos do Tesouro + vinculados a emendas parlamentares. Reúne classificações orçamentárias, autor da emenda, + localização (UF e município) e valores agregados de despesas empenhadas, liquidadas e pagas. + Os dados são extraídos da tabela empenhos_tesouro_emendas_parlamentares do SIAFI, com + padronização de datas e valores monetários. + meta: + tags: + - bronze + columns: + - name: emissao_mes + description: "Mês/ano de emissão da nota de empenho (primeiro dia do mês)." + - name: emissao_dia + description: "Data de emissão da nota de empenho." + - name: programa_governo + description: "Código do programa de governo no orçamento." + - name: programa_governo_descricao + description: "Descrição do programa de governo." + - name: acao_governo + description: "Código da ação de governo associada à emenda." + - name: acao_governo_descricao + description: "Descrição da ação de governo associada à emenda." + - name: autor_emendas_orcamento + description: "Identificador do autor da emenda no orçamento." + - name: autor_emendas_orcamento_descricao + description: "Descrição completa do autor da emenda no orçamento, incluindo indicação da emenda." + - name: autor_emendas_orcamento_nome + description: "Nome do autor da emenda em formato Title Case, extraído do texto completo antes da barra." + - name: uf_pt + description: "UF de aplicação da programação orçamentária (PT)." + - name: uf_pt_descricao + description: "Descrição da UF da programação orçamentária." + - name: municipio_pt + description: "Município de aplicação da programação orçamentária." + - name: ne_ccor + description: "Código da NE/CCOR no SIAFI." + - name: ne_num_processo + description: "Número do processo administrativo associado à NE." + - name: ne_info_complementar + description: "Informações complementares cadastradas na NE." + - name: ne_ccor_descricao + description: "Descrição textual da NE/CCOR." + - name: doc_observacao + description: "Observações adicionais registradas no documento SIAFI." + - name: grupo_despesa + description: "Código do grupo de despesa orçamentária." + - name: grupo_despesa_descricao + description: "Descrição do grupo de despesa orçamentária." + - name: natureza_despesa + description: "Código da natureza de despesa." + - name: natureza_despesa_descricao + description: "Descrição da natureza de despesa." + - name: modalidade_aplicacao + description: "Código da modalidade de aplicação." + - name: modalidade_aplicacao_descricao + description: "Descrição da modalidade de aplicação." + - name: ne_ccor_favorecido + description: "Identificador do favorecido na NE/CCOR." + - name: ne_ccor_favorecido_descricao + description: "Nome/descrição do favorecido na NE/CCOR." + - name: ne_ccor_ano_emissao + description: "Ano de emissão da NE/CCOR." + - name: ptres + description: "PTRES associado à despesa da emenda." + - name: item_informacao + description: "Código do item de informação orçamentária." + - name: item_informacao_descricao + description: "Descrição do item de informação orçamentária." + - name: despesas_empenhadas + description: "Valor total das despesas empenhadas para a combinação de classificações." + - name: despesas_liquidadas + description: "Valor total das despesas liquidadas para a combinação de classificações." + - name: despesas_pagas + description: "Valor total das despesas pagas para a combinação de classificações." + - name: dt_ingest + description: "Data e hora (UTC-3 Brasília) da ingestão dos dados para a camada bronze." + + tests: + - verificacao_tipagem: + nome_tabela: 'emendas.tg_emendas' + nome_coluna: 'emissao_mes' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'emendas.tg_emendas' + nome_coluna: 'emissao_dia' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'emendas.tg_emendas' + nome_coluna: 'programa_governo' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.tg_emendas' + nome_coluna: 'grupo_despesa' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.tg_emendas' + nome_coluna: 'modalidade_aplicacao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.tg_emendas' + nome_coluna: 'ne_ccor_ano_emissao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.tg_emendas' + nome_coluna: 'ptres' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.tg_emendas' + nome_coluna: 'despesas_empenhadas' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.tg_emendas' + nome_coluna: 'despesas_liquidadas' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.tg_emendas' + nome_coluna: 'despesas_pagas' + tipo_esperado: 'numeric' diff --git a/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/tg_emendas.sql b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/tg_emendas.sql new file mode 100644 index 00000000..e13e8f4d --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/tg_emendas.sql @@ -0,0 +1,53 @@ +{{ config(materialized="table") }} + +with + tg_emendas_raw as ( + select + {{ target.schema }}.parse_date(emissao_mes) as emissao_mes, + to_date(emissao_dia, 'DD/MM/YYYY') as emissao_dia, + programa_governo::integer as programa_governo, + programa_governo_descricao::text as programa_governo_descricao, + acao_governo::text as acao_governo, + acao_governo_descricao::text as acao_governo_descricao, + autor_emendas_orcamento::text as autor_emendas_orcamento, + autor_emendas_orcamento_descricao::text as autor_emendas_orcamento_descricao, + initcap( + trim( + regexp_replace( + split_part(autor_emendas_orcamento_descricao, '/', 1), + '\s+', + ' ', + 'g' + ) + ) + ) as autor_emendas_orcamento_nome, + uf_pt::text as uf_pt, + uf_pt_descricao::text as uf_pt_descricao, + municipio_pt::text as municipio_pt, + ne_ccor::text as ne_ccor, + ne_num_processo::text as ne_num_processo, + ne_info_complementar::text as ne_info_complementar, + ne_ccor_descricao::text as ne_ccor_descricao, + doc_observacao::text as doc_observacao, + grupo_despesa::integer as grupo_despesa, + grupo_despesa_descricao::text as grupo_despesa_descricao, + natureza_despesa::text as natureza_despesa, + natureza_despesa_descricao::text as natureza_despesa_descricao, + modalidade_aplicacao::integer as modalidade_aplicacao, + modalidade_aplicacao_descricao::text as modalidade_aplicacao_descricao, + ne_ccor_favorecido::text as ne_ccor_favorecido, + ne_ccor_favorecido_descricao::text as ne_ccor_favorecido_descricao, + ne_ccor_ano_emissao::integer as ne_ccor_ano_emissao, + ptres::integer as ptres, + item_informacao::text as item_informacao, + item_informacao_descricao::text as item_informacao_descricao, + {{ parse_financial_value("despesas_empenhadas") }} as despesas_empenhadas, + {{ parse_financial_value("despesas_liquidadas") }} as despesas_liquidadas, + {{ parse_financial_value("despesas_pagas") }} as despesas_pagas, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("siafi", "empenhos_tesouro_emendas_parlamentares") }} + ) + +select * +from tg_emendas_raw + diff --git a/airflow_lappis/dags/dbt/mir/models/sources.yml b/airflow_lappis/dags/dbt/mir/models/sources.yml index 3d34b609..e1891634 100644 --- a/airflow_lappis/dags/dbt/mir/models/sources.yml +++ b/airflow_lappis/dags/dbt/mir/models/sources.yml @@ -30,4 +30,5 @@ sources: - name: siafi schema: siafi tables: - - name: empenhos_tesouro_parlamentares \ No newline at end of file + - name: empenhos_tesouro_parlamentares + - name: empenhos_tesouro_emendas_parlamentares \ No newline at end of file From eac9e75f6d06c7eac7b040271228cbbc3005380a Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Sat, 28 Mar 2026 20:00:13 -0300 Subject: [PATCH 228/317] =?UTF-8?q?chore:=20restrutura=C3=A7=C3=A3o=20de?= =?UTF-8?q?=20schemas=20dados=5Fabertos=20dbt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- airflow_lappis/dags/dbt/mir/dbt_project.yml | 7 +- .../bronze/deputados.sql | 0 .../bronze/schema.yml | 89 +++++++++++++++++- .../bronze/senadores.sql | 0 .../models/deputados_dbt/bronze/schema.yaml | 92 ------------------- 5 files changed, 89 insertions(+), 99 deletions(-) rename airflow_lappis/dags/dbt/mir/models/{deputados_dbt => dados_abertos_dbt}/bronze/deputados.sql (100%) rename airflow_lappis/dags/dbt/mir/models/{senadores_dbt => dados_abertos_dbt}/bronze/schema.yml (53%) rename airflow_lappis/dags/dbt/mir/models/{senadores_dbt => dados_abertos_dbt}/bronze/senadores.sql (100%) delete mode 100644 airflow_lappis/dags/dbt/mir/models/deputados_dbt/bronze/schema.yaml diff --git a/airflow_lappis/dags/dbt/mir/dbt_project.yml b/airflow_lappis/dags/dbt/mir/dbt_project.yml index 8943454b..7caa510f 100755 --- a/airflow_lappis/dags/dbt/mir/dbt_project.yml +++ b/airflow_lappis/dags/dbt/mir/dbt_project.yml @@ -24,12 +24,9 @@ models: emendas_dbt: +materialized: table +schema: emendas - deputados_dbt: + dados_abertos_dbt: +materialized: table - +schema: deputados - senadores_dbt: - +materialized: table - +schema: senadores + +schema: dados_abertos empenhos_ted_dbt: +materialized: table +schema: siafi_dbt diff --git a/airflow_lappis/dags/dbt/mir/models/deputados_dbt/bronze/deputados.sql b/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/deputados.sql similarity index 100% rename from airflow_lappis/dags/dbt/mir/models/deputados_dbt/bronze/deputados.sql rename to airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/deputados.sql diff --git a/airflow_lappis/dags/dbt/mir/models/senadores_dbt/bronze/schema.yml b/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/schema.yml similarity index 53% rename from airflow_lappis/dags/dbt/mir/models/senadores_dbt/bronze/schema.yml rename to airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/schema.yml index 37c5352c..13cbc8ba 100644 --- a/airflow_lappis/dags/dbt/mir/models/senadores_dbt/bronze/schema.yml +++ b/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/schema.yml @@ -1,10 +1,95 @@ version: 2 models: - - # Senadores DBT + # Dados Abertos DBT ## Bronze + - name: deputados + description: > + Tabela com informações básicas sobre deputados federais obtidas por meio da API de Dados Abertos da Câmara dos Deputados. + Contém dados cadastrais e institucionais como identificador, nome parlamentar, partido, unidade federativa, + legislatura e e-mail institucional. + Esta tabela representa a camada bronze do pipeline, aplicando padronização de tipos, + pequenas transformações e ajuste de fuso horário na data de ingestão. + Os dados são provenientes da fonte dados_abertos.deputados (camada raw) e + passam por conversão explícita de tipos para garantir consistência e rastreabilidade. + meta: + tags: + - bronze + columns: + - name: id + description: > + Identificador único do deputado na API da Câmara. + Utilizado como chave primária e referência para relacionamentos com outras tabelas. + + - name: nome + description: > + Nome parlamentar do deputado conforme registrado na Câmara dos Deputados. + + - name: siglapartido + description: > + Sigla do partido ao qual o deputado está filiado na legislatura informada. + + - name: siglauf + description: > + Sigla da Unidade Federativa (UF) que o deputado representa. + + - name: idlegislatura + description: > + Identificador numérico da legislatura à qual o deputado está vinculado. + + - name: urlfoto + description: > + URL da foto oficial do deputado disponibilizada pela Câmara dos Deputados. + + - name: email + description: > + Endereço de e-mail institucional do deputado na Câmara dos Deputados. + + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. + tests: + - verificacao_tipagem: + nome_tabela: 'deputados.deputados' + nome_coluna: 'id' + tipo_esperado: 'integer' + + - verificacao_tipagem: + nome_tabela: 'deputados.deputados' + nome_coluna: 'nome' + tipo_esperado: 'text' + + - verificacao_tipagem: + nome_tabela: 'deputados.deputados' + nome_coluna: 'siglapartido' + tipo_esperado: 'text' + + - verificacao_tipagem: + nome_tabela: 'deputados.deputados' + nome_coluna: 'siglauf' + tipo_esperado: 'text' + + - verificacao_tipagem: + nome_tabela: 'deputados.deputados' + nome_coluna: 'idlegislatura' + tipo_esperado: 'integer' + + - verificacao_tipagem: + nome_tabela: 'deputados' + nome_coluna: 'urlfoto' + tipo_esperado: 'text' + + - verificacao_tipagem: + nome_tabela: 'deputados.deputados' + nome_coluna: 'email' + tipo_esperado: 'text' + + - verificacao_tipagem: + nome_tabela: 'deputados.deputados' + nome_coluna: 'dt_ingest' + tipo_esperado: 'timestamp with time zone' + - name: senadores description: > Tabela com informações básicas sobre senadores federais obtidas por meio da API de Dados Abertos do Senado Federal. diff --git a/airflow_lappis/dags/dbt/mir/models/senadores_dbt/bronze/senadores.sql b/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/senadores.sql similarity index 100% rename from airflow_lappis/dags/dbt/mir/models/senadores_dbt/bronze/senadores.sql rename to airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/senadores.sql diff --git a/airflow_lappis/dags/dbt/mir/models/deputados_dbt/bronze/schema.yaml b/airflow_lappis/dags/dbt/mir/models/deputados_dbt/bronze/schema.yaml deleted file mode 100644 index 852b4e46..00000000 --- a/airflow_lappis/dags/dbt/mir/models/deputados_dbt/bronze/schema.yaml +++ /dev/null @@ -1,92 +0,0 @@ -version: 2 - -models: - - # Deputados DBT - - ## Bronze - - name: deputados - description: > - Tabela com informações básicas sobre deputados federais obtidas por meio da API de Dados Abertos da Câmara dos Deputados. - Contém dados cadastrais e institucionais como identificador, nome parlamentar, partido, unidade federativa, - legislatura e e-mail institucional. - Esta tabela representa a camada bronze do pipeline, aplicando padronização de tipos, - pequenas transformações e ajuste de fuso horário na data de ingestão. - Os dados são provenientes da fonte dados_abertos.deputados (camada raw) e - passam por conversão explícita de tipos para garantir consistência e rastreabilidade. - meta: - tags: - - bronze - columns: - - name: id - description: > - Identificador único do deputado na API da Câmara. - Utilizado como chave primária e referência para relacionamentos com outras tabelas. - - - name: nome - description: > - Nome parlamentar do deputado conforme registrado na Câmara dos Deputados. - - - name: siglapartido - description: > - Sigla do partido ao qual o deputado está filiado na legislatura informada. - - - name: siglauf - description: > - Sigla da Unidade Federativa (UF) que o deputado representa. - - - name: idlegislatura - description: > - Identificador numérico da legislatura à qual o deputado está vinculado. - - - name: urlfoto - description: > - URL da foto oficial do deputado disponibilizada pela Câmara dos Deputados. - - - name: email - description: > - Endereço de e-mail institucional do deputado na Câmara dos Deputados. - - - name: dt_ingest - description: > - Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. - tests: - - verificacao_tipagem: - nome_tabela: 'deputados.deputados' - nome_coluna: 'id' - tipo_esperado: 'integer' - - - verificacao_tipagem: - nome_tabela: 'deputados.deputados' - nome_coluna: 'nome' - tipo_esperado: 'text' - - - verificacao_tipagem: - nome_tabela: 'deputados.deputados' - nome_coluna: 'siglapartido' - tipo_esperado: 'text' - - - verificacao_tipagem: - nome_tabela: 'deputados.deputados' - nome_coluna: 'siglauf' - tipo_esperado: 'text' - - - verificacao_tipagem: - nome_tabela: 'deputados.deputados' - nome_coluna: 'idlegislatura' - tipo_esperado: 'integer' - - - verificacao_tipagem: - nome_tabela: 'deputados' - nome_coluna: 'urlfoto' - tipo_esperado: 'text' - - - verificacao_tipagem: - nome_tabela: 'deputados.deputados' - nome_coluna: 'email' - tipo_esperado: 'text' - - - verificacao_tipagem: - nome_tabela: 'deputados.deputados' - nome_coluna: 'dt_ingest' - tipo_esperado: 'timestamp with time zone' \ No newline at end of file From 5681a8c1c151017f487bd63fb6122fabefa7e9ee Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Sat, 28 Mar 2026 20:05:02 -0300 Subject: [PATCH 229/317] chore: padronizando sources names --- airflow_lappis/dags/dbt/mir/dbt_project.yml | 1 + .../dags/dbt/mir/models/dados_abertos_dbt/bronze/deputados.sql | 2 +- airflow_lappis/dags/dbt/mir/models/sources.yml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/airflow_lappis/dags/dbt/mir/dbt_project.yml b/airflow_lappis/dags/dbt/mir/dbt_project.yml index 7caa510f..9fccdb95 100755 --- a/airflow_lappis/dags/dbt/mir/dbt_project.yml +++ b/airflow_lappis/dags/dbt/mir/dbt_project.yml @@ -7,6 +7,7 @@ profile: mir model-paths: ["models"] analysis-paths: ["analyses"] +seed-paths: ["seeds"] test-paths: ["tests"] macro-paths: ["macros"] diff --git a/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/deputados.sql b/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/deputados.sql index 24e0a1b3..c523a197 100644 --- a/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/deputados.sql +++ b/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/deputados.sql @@ -12,7 +12,7 @@ with urlfoto::text as urlfoto, email::text as email, (dt_ingest || '-03:00')::timestamptz as dt_ingest - from {{ source("dados_abertos", "deputados") }} + from {{ source("camara_deputados", "deputados") }} ) select * diff --git a/airflow_lappis/dags/dbt/mir/models/sources.yml b/airflow_lappis/dags/dbt/mir/models/sources.yml index 3d34b609..96498643 100644 --- a/airflow_lappis/dags/dbt/mir/models/sources.yml +++ b/airflow_lappis/dags/dbt/mir/models/sources.yml @@ -17,7 +17,7 @@ sources: - name: metas_especiais - name: finalidades_especiais - - name: dados_abertos + - name: camara_deputados schema: camara_deputados tables: - name: deputados From 641523d8fbdc6c6ff1a15013362ab2b5b2a94c15 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Sun, 29 Mar 2026 20:46:32 -0300 Subject: [PATCH 230/317] feat(silver): silver parlamentares e logo seed --- .../silver/parlamentares.sql | 77 ++++++++++++++++++ .../dados_abertos_dbt/silver/schema.yml | 80 +++++++++++++++++++ .../dags/dbt/mir/seeds/partidos_logo.csv | 29 +++++++ .../dags/dbt/mir/seeds/partidos_map.csv | 2 + airflow_lappis/dags/dbt/mir/seeds/schema.yml | 14 ++++ 5 files changed, 202 insertions(+) create mode 100644 airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/silver/parlamentares.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/silver/schema.yml create mode 100644 airflow_lappis/dags/dbt/mir/seeds/partidos_logo.csv create mode 100644 airflow_lappis/dags/dbt/mir/seeds/partidos_map.csv create mode 100644 airflow_lappis/dags/dbt/mir/seeds/schema.yml diff --git a/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/silver/parlamentares.sql b/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/silver/parlamentares.sql new file mode 100644 index 00000000..49a49aa5 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/silver/parlamentares.sql @@ -0,0 +1,77 @@ +{{ config(materialized='table') }} + +WITH +bronze_deputados AS ( + SELECT * FROM {{ ref('deputados') }} +), + +bronze_senadores AS ( + SELECT * FROM {{ ref('senadores') }} +), + +sigla_map AS ( + SELECT + TRIM(UPPER(sigla_origem)) AS sigla_origem, + TRIM(UPPER(sigla_canonica)) AS sigla_canonica + FROM {{ ref('partidos_map') }} +), + +parlamentares_unificados AS ( + SELECT + id AS id_parlamentar, + TRIM(UPPER(nome)) AS chave_join_nome, + nome AS nome_parlamentar, + 'Deputado' AS cargo_parlamentar, + siglapartido AS sigla_partido, + siglauf AS uf_parlamentar, + urlfoto AS url_foto, + email + FROM bronze_deputados + + UNION ALL + + SELECT + id AS id_parlamentar, + TRIM(UPPER(nome_parlamentar)) AS chave_join_nome, + nome_parlamentar AS nome_parlamentar, + 'Senador' AS cargo_parlamentar, + sigla_partido AS sigla_partido, + uf AS uf_parlamentar, + url_foto AS url_foto, + email + FROM bronze_senadores +), + +parlamentares_padronizados AS ( + SELECT + p.*, + COALESCE(m.sigla_canonica, p.sigla_partido) AS sigla_partido_padronizada + FROM parlamentares_unificados p + LEFT JOIN sigla_map m + ON TRIM(UPPER(p.sigla_partido)) = m.sigla_origem +), + +partidos_logo AS ( + SELECT + TRIM(UPPER(sigla)) AS chave_join_sigla_partido, + logo_url + FROM {{ ref('partidos_logo') }} +) + +SELECT + p.id_parlamentar, + p.chave_join_nome, + p.nome_parlamentar, + p.cargo_parlamentar, + + p.sigla_partido_padronizada AS sigla_partido, + + p.uf_parlamentar, + p.url_foto, + p.email, + pl.logo_url AS url_logo_partido + +FROM parlamentares_padronizados p + +LEFT JOIN partidos_logo pl + ON TRIM(UPPER(p.sigla_partido_padronizada)) = pl.chave_join_sigla_partido \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/silver/schema.yml b/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/silver/schema.yml new file mode 100644 index 00000000..48e0d81f --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/silver/schema.yml @@ -0,0 +1,80 @@ +version: 2 + +models: + - name: parlamentares + description: > + Tabela silver que unifica informações de deputados e senadores + em uma visão única de parlamentares, incluindo partido, UF, + foto e logo do partido. + meta: + tags: + - silver + columns: + - name: id_parlamentar + description: > + Identificador único do parlamentar na base unificada + de deputados e senadores. + - name: chave_join_nome + description: > + Chave de junção padronizada (nome em caixa alta e trimado), + utilizada para relacionar parlamentares com outros modelos. + - name: nome_parlamentar + description: > + Nome do parlamentar conforme registrado na base de origem. + - name: cargo_parlamentar + description: > + Cargo do parlamentar (por exemplo, Deputado ou Senador). + - name: sigla_partido + description: > + Sigla do partido ao qual o parlamentar está filiado. + - name: uf_parlamentar + description: > + Sigla da Unidade Federativa (UF) que o parlamentar representa. + - name: url_foto + description: > + URL da foto oficial do parlamentar na base de origem. + - name: email + description: > + Endereço de e-mail institucional do parlamentar. + - name: url_logo_partido + description: > + URL da imagem (logo) do partido do parlamentar, + proveniente do seed partidos_logo. + tests: + - verificacao_tipagem: + nome_tabela: 'dados_abertos.parlamentares' + nome_coluna: 'id_parlamentar' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'dados_abertos.parlamentares' + nome_coluna: 'chave_join_nome' + tipo_esperado: 'text' + - verificacao_tipagem: + nome_tabela: 'dados_abertos.parlamentares' + nome_coluna: 'nome_parlamentar' + tipo_esperado: 'text' + - verificacao_tipagem: + nome_tabela: 'dados_abertos.parlamentares' + nome_coluna: 'cargo_parlamentar' + tipo_esperado: 'text' + - verificacao_tipagem: + nome_tabela: 'dados_abertos.parlamentares' + nome_coluna: 'sigla_partido' + tipo_esperado: 'text' + - verificacao_tipagem: + nome_tabela: 'dados_abertos.parlamentares' + nome_coluna: 'uf_parlamentar' + tipo_esperado: 'text' + - verificacao_tipagem: + nome_tabela: 'dados_abertos.parlamentares' + nome_coluna: 'url_foto' + tipo_esperado: 'text' + - verificacao_tipagem: + nome_tabela: 'dados_abertos.parlamentares' + nome_coluna: 'email' + tipo_esperado: 'text' + - verificacao_tipagem: + nome_tabela: 'dados_abertos.parlamentares' + nome_coluna: 'url_logo_partido' + tipo_esperado: 'text' + diff --git a/airflow_lappis/dags/dbt/mir/seeds/partidos_logo.csv b/airflow_lappis/dags/dbt/mir/seeds/partidos_logo.csv new file mode 100644 index 00000000..2dfcb601 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/seeds/partidos_logo.csv @@ -0,0 +1,29 @@ +sigla,nome,logo_url +AVANTE,Avante,https://upload.wikimedia.org/wikipedia/commons/7/7a/Bandeira-partido-avante.png +CIDADANIA,Cidadania,https://upload.wikimedia.org/wikipedia/commons/d/d7/Logo_do_Cidadania_23.png +DC,Democracia Cristã,https://upload.wikimedia.org/wikipedia/commons/c/cd/Bandeira-democracia-crist%C3%A3.png +MDB,Movimento Democrático Brasileiro,https://upload.wikimedia.org/wikipedia/commons/a/a8/Movimento_Democr%C3%A1tico_Brasileiro_%282017%29.png +MISSÃO,Missão,https://upload.wikimedia.org/wikipedia/commons/6/6e/Partido_Miss%C3%A3o_logo_%28dark%29.svg +NOVO,Partido Novo,https://upload.wikimedia.org/wikipedia/commons/6/64/Partido_Novo_logo_%282020%29.svg +PCdoB,Partido Comunista do Brasil,https://upload.wikimedia.org/wikipedia/commons/e/e5/PCdoB_flag.svg +PCO,Partido da Causa Operária,https://upload.wikimedia.org/wikipedia/commons/4/4a/Bandeira_do_Partido_da_Causa_Oper%C3%A1ria%2C_do_Brasil.png +PDT,Partido Democrático Trabalhista,https://upload.wikimedia.org/wikipedia/commons/5/51/LogoPDT.svg +PL,Partido Liberal,https://upload.wikimedia.org/wikipedia/commons/1/12/Partido_Liberal_%28Brazil%29_logo.svg +PMB,Partido da Mulher Brasileira,https://upload.wikimedia.org/wikipedia/commons/f/f2/Logomarca_do_Partido_da_Mulher_Brasileira_%282008%29.png +PODE,Podemos,https://upload.wikimedia.org/wikipedia/commons/9/99/Logo_Podemos_20.png +PP,Progressistas,https://upload.wikimedia.org/wikipedia/commons/0/0d/Partido_Progressista_%28Brazil%29_logo.svg +PRD,Partido Renovação Democrática,https://upload.wikimedia.org/wikipedia/commons/2/2c/PRD_logo.png +PROS,Partido Republicano da Ordem Social,https://upload.wikimedia.org/wikipedia/commons/8/8a/PROS_logo.png +PRTB,Partido Renovador Trabalhista Brasileiro,https://upload.wikimedia.org/wikipedia/commons/7/76/PRTB-LOGO-04-1024x393.png +PSB,Partido Socialista Brasileiro,https://upload.wikimedia.org/wikipedia/commons/1/19/Partido_Socialista_Brasileiro_logo.png +PSC,Partido Social Cristão,https://upload.wikimedia.org/wikipedia/commons/a/a9/PSC_logo%28cortado%29.png +PSD,Partido Social Democrático,https://upload.wikimedia.org/wikipedia/commons/0/0c/PSD_Brazil_logo.svg +PSDB,Partido da Social Democracia Brasileira,https://upload.wikimedia.org/wikipedia/commons/9/9c/Logomarca_do_Partido_da_Social_Democracia_Brasileira.png +PSOL,Partido Socialismo e Liberdade,https://upload.wikimedia.org/wikipedia/commons/f/f4/Logo_PSOL_roxo.svg +PT,Partido dos Trabalhadores,https://upload.wikimedia.org/wikipedia/commons/b/be/Logo_do_Partido_dos_Trabalhadores.svg +PV,Partido Verde,https://upload.wikimedia.org/wikipedia/commons/4/46/Logomarca_do_Partido_Verde.svg +REDE,Rede Sustentabilidade,https://upload.wikimedia.org/wikipedia/commons/2/2d/Logomarca_da_Rede_Sustentabilidade_(REDE)%2C_do_Brasil.png +REPUBLICANOS,Republicanos,https://upload.wikimedia.org/wikipedia/commons/3/3c/Logomarca_do_Partido_Republicano_Brasileiro_%282005%E2%80%932012%29.png +SOLIDARIEDADE,Solidariedade,https://upload.wikimedia.org/wikipedia/commons/f/fe/Logomarca_do_Partido_Solidariedade.png +UNIÃO,União Brasil,https://upload.wikimedia.org/wikipedia/commons/7/73/Uni%C3%A3o_Brasil_logo.svg +UP,Unidade Popular,https://upload.wikimedia.org/wikipedia/commons/b/bd/UP_Flag2.svg \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/seeds/partidos_map.csv b/airflow_lappis/dags/dbt/mir/seeds/partidos_map.csv new file mode 100644 index 00000000..ee1d85fe --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/seeds/partidos_map.csv @@ -0,0 +1,2 @@ +sigla_origem,sigla_canonica +PODEMOS,PODE \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/seeds/schema.yml b/airflow_lappis/dags/dbt/mir/seeds/schema.yml new file mode 100644 index 00000000..e5676468 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/seeds/schema.yml @@ -0,0 +1,14 @@ +version: 2 + +seeds: + - name: partidos_logo + config: + column_types: + sigla: text + nome: text + logo_url: text + - name: partidos_map + config: + column_types: + sigla_origem: text + sigla_canonica: text From 9e7fc72987ee7888e9c9717466a176de2dbf5221 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Sun, 29 Mar 2026 20:46:57 -0300 Subject: [PATCH 231/317] test: testagem de cruzamento logo partidos --- .../tests/test_parlamentares_logo_partido.sql | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 airflow_lappis/dags/dbt/mir/tests/test_parlamentares_logo_partido.sql diff --git a/airflow_lappis/dags/dbt/mir/tests/test_parlamentares_logo_partido.sql b/airflow_lappis/dags/dbt/mir/tests/test_parlamentares_logo_partido.sql new file mode 100644 index 00000000..bab1d974 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/tests/test_parlamentares_logo_partido.sql @@ -0,0 +1,19 @@ +-- Falha (ou gera warning) se existir algum parlamentar com sigla de partido +-- que não tenha correspondência no seed partidos_logo. + +{{ config(severity='warn') }} + +with parlamentares as ( + select * from {{ ref('parlamentares') }} +), +partidos_logo as ( + select * from {{ ref('partidos_logo') }} +) + +select distinct + p.sigla_partido +from parlamentares p +left join partidos_logo pl + on upper(trim(p.sigla_partido)) = upper(trim(pl.sigla)) +where p.sigla_partido is not null + and pl.sigla is null From c2820627aa3fa427095cbd3e0ccb083e7bd538e7 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Tue, 31 Mar 2026 14:53:29 -0300 Subject: [PATCH 232/317] =?UTF-8?q?chore:=20remove=20declara=C3=A7=C3=A3o?= =?UTF-8?q?=20desnecess=C3=A1ria=20cloude?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ..._execucao_outras_fontes_mcid_ingest_dag.py | 10 ---------- ...mpenho_emendas_parlamentares_ingest_dag.py | 1 - .../orcamento_mcid_por_acao_ingest_dag.py | 20 ------------------- 3 files changed, 31 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mcid/dotacao_execucao_outras_fontes_mcid_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mcid/dotacao_execucao_outras_fontes_mcid_ingest_dag.py index 4e4636bf..85525dae 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mcid/dotacao_execucao_outras_fontes_mcid_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mcid/dotacao_execucao_outras_fontes_mcid_ingest_dag.py @@ -157,20 +157,10 @@ def insert_data_to_db(**context: Dict[str, Any]) -> None: postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) - unique_key = [ - "unidade_orcamentaria_codigo", - "acao_governo_codigo", - "plano_orcamentario_codigo", - "plano_orcamentario_descricao", - "elemento_despesa_codigo", - "ug_executora_codigo", - ] db.insert_data( data, "dotacao_execucao_outras_fontes_mcid", - # conflict_fields=unique_key, - # primary_key=unique_key, schema="siafi", ) logging.info("Dados inseridos com sucesso no banco de dados.") diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mcid/empenho_emendas_parlamentares_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mcid/empenho_emendas_parlamentares_ingest_dag.py index c2cb5789..21524328 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mcid/empenho_emendas_parlamentares_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mcid/empenho_emendas_parlamentares_ingest_dag.py @@ -108,7 +108,6 @@ def insert_data_to_db(**context: Dict[str, Any]) -> None: raise AirflowSkipException( "Nenhum dado foi encontrado para inserção no BD" ) - return df = pd.read_csv(io.StringIO(csv_data), skiprows=[1, 2, 3]) df = df[df["ne_ccor_ano_emissao"].astype(str).str.startswith("20")] diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mcid/orcamento_mcid_por_acao_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mcid/orcamento_mcid_por_acao_ingest_dag.py index c1f93682..7df16e06 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mcid/orcamento_mcid_por_acao_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mcid/orcamento_mcid_por_acao_ingest_dag.py @@ -168,30 +168,10 @@ def insert_data_to_db(**context: Dict[str, Any]) -> None: postgres_conn_str = get_postgres_conn() db = ClientPostgresDB(postgres_conn_str) - unique_key = [ - "acao_governo_codigo", - "ne_ccor", - "programa_governo_codigo", - "pt", - "ptres", - "pl_codigo", - "ug_executora_codigo", - "natureza_despesa_codigo", - "fonte_recursos_detalhada", - "plano_orcamentario_ug_executora_codigo", - "plano_orcamentario_cod1", - "plano_orcamentario_cod2", - "plano_orcamentario_programa", - "plano_orcamentario_acao_orcamentaria", - "plano_orcamentario_medida", - "plano_orcamentario_descricao", - ] db.insert_data( data, "orcamento_mcid_por_acao", - # conflict_fields=unique_key, - # primary_key=unique_key, schema="siafi", ) logging.info("Dados inseridos com sucesso no banco de dados.") From decb10ebcb1b847464004ef41fb02072e948619d Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Thu, 2 Apr 2026 09:06:39 -0300 Subject: [PATCH 233/317] feat: bulk logic IMAP eficience Co-authored-by: Davi de Aguiar Vieira --- airflow_lappis/plugins/cliente_email.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/airflow_lappis/plugins/cliente_email.py b/airflow_lappis/plugins/cliente_email.py index 7fa5ce41..3f0c1b55 100755 --- a/airflow_lappis/plugins/cliente_email.py +++ b/airflow_lappis/plugins/cliente_email.py @@ -67,7 +67,8 @@ def fetch_email_with_zip( today = datetime.now(pytz.timezone("America/Sao_Paulo")).date() zip_payloads: List[bytes] = [] with MailBox(imap_server).login(email, password) as mailbox: - for msg in mailbox.fetch(AND(date=today, from_=sender_email, subject=subject)): + # bulk=True: single IMAP FETCH command for all messages (avoids overquota) + for msg in mailbox.fetch(AND(date=today, from_=sender_email, subject=subject), bulk=True): for attachment in msg.attachments: if attachment.filename.lower().endswith(".zip"): zip_payloads.append(cast(bytes, attachment.payload)) From b171cced291323c453379b1282423b25303823ca Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Thu, 2 Apr 2026 09:14:58 -0300 Subject: [PATCH 234/317] feat: task unica ingest & insert xcom bloat prevent Co-authored-by: Davi de Aguiar Vieira --- ...penhos_tesouro_parlamentares_ingest_dag.py | 166 +++++++----------- 1 file changed, 67 insertions(+), 99 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/empenhos_tesouro_parlamentares_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/empenhos_tesouro_parlamentares_ingest_dag.py index b7161bb4..2d892e6b 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/empenhos_tesouro_parlamentares_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/empenhos_tesouro_parlamentares_ingest_dag.py @@ -1,4 +1,4 @@ -from typing import Dict, Any, Optional +from typing import Dict, Any, List from airflow import DAG from airflow.operators.python import PythonOperator from airflow.models import Variable @@ -6,11 +6,9 @@ import logging import json from schedule_loader import get_dynamic_schedule -from cliente_email import fetch_and_process_email +from cliente_email import fetch_email_with_zip, extract_csv_from_zip from cliente_postgres import ClientPostgresDB from postgres_helpers import get_postgres_conn -import pandas as pd -import io # Configurações básicas da DAG default_args = { @@ -47,6 +45,18 @@ 23: "restos_a_pagar_pagos", } +UNIQUE_KEY = [ + "ne_ccor", + "natureza_despesa", + "doc_observacao", + "ne_ccor_ano_emissao", + "emissao_dia", + "emissao_mes", + "despesas_empenhadas", + "despesas_liquidadas", + "despesas_pagas", +] + EMAIL_SUBJECT = "notas_de_empenho_ano_atual" SKIPROWS = 8 @@ -61,101 +71,59 @@ tags=["MIR", "email", "empenhos", "tesouro", "parlamentares"], ) as dag: - def process_email_data(**context: Dict[str, Any]) -> Optional[Any]: + def _get_db_client() -> ClientPostgresDB: + return ClientPostgresDB(get_postgres_conn("postgres_mir")) + + def _insert_dataframe(df, db: ClientPostgresDB) -> int: + df = df[df["ne_ccor_ano_emissao"].astype(str).str.startswith("20")] + records = df.to_dict(orient="records") + for r in records: + r["dt_ingest"] = datetime.now().isoformat() + + db.insert_data( + records, + "empenhos_tesouro_parlamentares", + conflict_fields=UNIQUE_KEY, + primary_key=UNIQUE_KEY, + schema="siafi", + ) + return len(records) + + def fetch_and_ingest(**context: Dict[str, Any]) -> Dict[str, int]: + """Processa cada anexo e ingere imediatamente, evitando acúmulo em memória.""" creds = json.loads(Variable.get("email_credentials")) - EMAIL = creds["email"] - PASSWORD = creds["password"] - IMAP_SERVER = creds["imap_server"] - SENDER_EMAIL = creds["sender_email"] - - try: - logging.info("Iniciando o processamento dos emails...") - csv_data = fetch_and_process_email( - IMAP_SERVER, - EMAIL, - PASSWORD, - SENDER_EMAIL, - EMAIL_SUBJECT, - COLUMN_MAPPING, - skiprows=SKIPROWS, - ) - if not csv_data: - logging.warning( - "Nenhum CSV valido foi extraido dos e-mails encontrados " - "para o assunto esperado." - ) - return None - - logging.info( - "CSV processado com sucesso. Dados encontrados: %s", len(csv_data) - ) - return csv_data - except Exception as e: - logging.error("Erro no processamento dos emails: %s", str(e)) - raise - - def insert_data_to_db(**context: Dict[str, Any]) -> None: - """ - Função para inserir os dados no banco de dados. - Os dados do CSV são recuperados do XCom. - """ - try: - task_instance: Any = context["ti"] - csv_data: Any = task_instance.xcom_pull(task_ids="process_emails") - - if not csv_data: - logging.warning("Nenhum dado para inserir no banco.") - return - - df = pd.read_csv(io.StringIO(csv_data)) - df = df[df["ne_ccor_ano_emissao"].astype(str).str.startswith("20")] - data = df.to_dict(orient="records") - - # Adicionar dt_ingest a cada registro - for record in data: - record["dt_ingest"] = datetime.now().isoformat() - - postgres_conn_str = get_postgres_conn("postgres_mir") - db = ClientPostgresDB(postgres_conn_str) - - unique_key = [ - "ne_ccor", - "natureza_despesa", - "doc_observacao", - "ne_ccor_ano_emissao", - "emissao_dia", - "emissao_mes", - "despesas_empenhadas", - "despesas_liquidadas", - "despesas_pagas", - ] - - db.insert_data( - data, - "empenhos_tesouro_parlamentares", - conflict_fields=unique_key, - primary_key=unique_key, - schema="siafi", - ) - logging.info("Dados inseridos com sucesso no banco de dados.") - except Exception as e: - logging.error("Erro ao inserir dados no banco: %s", str(e)) - raise - - # Tarefa 1: Processar os e-mails e retornar CSV - process_emails_task = PythonOperator( - task_id="process_emails", - python_callable=process_email_data, - provide_context=True, - ) - - # Tarefa 2: Inserir os dados no banco de dados - insert_to_db_task = PythonOperator( - task_id="insert_to_db", - python_callable=insert_data_to_db, - provide_context=True, + zip_payloads: List[bytes] = fetch_email_with_zip( + creds["imap_server"], + creds["email"], + creds["password"], + creds["sender_email"], + EMAIL_SUBJECT, + ) + + if not zip_payloads: + logging.warning("Nenhum anexo ZIP encontrado.") + return {"attachments": 0, "records": 0} + + logging.info("Total de anexos ZIP encontrados: %s", len(zip_payloads)) + + db = _get_db_client() + total_records = 0 + + for idx, payload in enumerate(zip_payloads, 1): + df = extract_csv_from_zip(payload, COLUMN_MAPPING, SKIPROWS) + if df is not None: + count = _insert_dataframe(df, db) + total_records += count + logging.info("Anexo %s: %s registros inseridos", idx, count) + else: + logging.warning("Anexo %s ignorado (CSV inválido)", idx) + del df # Libera memória imediatamente + + logging.info("Total: %s anexos, %s registros", len(zip_payloads), total_records) + return {"attachments": len(zip_payloads), "records": total_records} + + PythonOperator( + task_id="fetch_and_ingest", + python_callable=fetch_and_ingest, ) - - # Fluxo da DAG - process_emails_task >> insert_to_db_task From 93eab5b6cca212a531cb8ad9c530accbaa0f46a4 Mon Sep 17 00:00:00 2001 From: Luana Date: Thu, 2 Apr 2026 11:00:58 -0300 Subject: [PATCH 235/317] fix: incluindo todos os deputados ativos e nao ativos --- .../data_ingest/dados_abertos/deputados_ingest_dag.py | 10 +++++++++- airflow_lappis/plugins/cliente_deputados.py | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/dados_abertos/deputados_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_abertos/deputados_ingest_dag.py index 9afe396c..b7ca23ff 100644 --- a/airflow_lappis/dags/data_ingest/dados_abertos/deputados_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_abertos/deputados_ingest_dag.py @@ -32,8 +32,16 @@ def fetch_and_store_deputados() -> None: deputados_data = api.get_all_deputados() if deputados_data and len(deputados_data) > 0: + vistos= set() + lista_limpa = [] + for item in deputados_data: - item["dt_ingest"] = datetime.now().isoformat() + if item["id"] not in vistos: + item["dt_ingest"] = datetime.now().isoformat() + lista_limpa.append(item) + vistos.add(item["id"]) + + deputados_data = lista_limpa logging.info( f"[deputados_ingest_dag.py] Inserindo " diff --git a/airflow_lappis/plugins/cliente_deputados.py b/airflow_lappis/plugins/cliente_deputados.py index 0411103c..2a3e3a03 100644 --- a/airflow_lappis/plugins/cliente_deputados.py +++ b/airflow_lappis/plugins/cliente_deputados.py @@ -50,7 +50,7 @@ def get_all_deputados(self) -> list: pagina = 1 while True: - params = {"pagina": pagina, "itens": 100} + params = {"pagina": pagina, "itens": 1000, "dataInicio": "1823-01-01"} deputados = self.get_deputados(**params) if not deputados: From 08c9312515459b8ccda9a350c2fcafe4425f4ac7 Mon Sep 17 00:00:00 2001 From: Ingrid Alves Date: Thu, 2 Apr 2026 17:11:46 -0300 Subject: [PATCH 236/317] fix: ajustes na lista dos senadores para abranger os inativos e ativos --- .../data_ingest/dados_abertos/senadores_ingest_dag.py | 2 +- airflow_lappis/plugins/cliente_senadores.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/dados_abertos/senadores_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_abertos/senadores_ingest_dag.py index 89804420..00b574ad 100644 --- a/airflow_lappis/dags/data_ingest/dados_abertos/senadores_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_abertos/senadores_ingest_dag.py @@ -29,7 +29,7 @@ def fetch_and_store_senadores() -> None: postgres_conn_str = get_postgres_conn("postgres_mir") db = ClientPostgresDB(postgres_conn_str) - senadores_data = api.get_senadores_atuais() + senadores_data = api.get_senadores_por_legislatura() if senadores_data and len(senadores_data) > 0: registros_limpos = [] diff --git a/airflow_lappis/plugins/cliente_senadores.py b/airflow_lappis/plugins/cliente_senadores.py index c58f59d3..8fcc2b4b 100644 --- a/airflow_lappis/plugins/cliente_senadores.py +++ b/airflow_lappis/plugins/cliente_senadores.py @@ -18,12 +18,12 @@ def __init__(self) -> None: f"[cliente_senadores.py] Initialized ClienteSenadores em: {ClienteSenadores.BASE_URL}" ) - def get_senadores_atuais(self) -> list: + def get_senadores_por_legislatura(self) -> list: """ - Obtém a lista de senadores em exercício. + Obtém a lista de senadores ativose inativos. O Senado geralmente retorna tudo em uma única chamada, sem paginação complexa como a Câmara. """ - endpoint = "/senador/lista/atual" + endpoint = "/senador/lista/legislatura/0/100" logging.info("[cliente_senadores.py] Fetching senadores atuais") status, data = self.request( @@ -31,9 +31,9 @@ def get_senadores_atuais(self) -> list: ) if status == http.HTTPStatus.OK and isinstance(data, dict): - # A estrutura do JSON do Senado é: ListaParlamentarEmExercicio -> Parlamentares -> Parlamentar + # A estrutura do JSON do Senado é: ListaParlamentarLegislatura -> Parlamentares -> Parlamentar try: - lista_root = data.get("ListaParlamentarEmExercicio", {}) + lista_root = data.get("ListaParlamentarLegislatura", {}) parlamentares = lista_root.get("Parlamentares", {}).get("Parlamentar", []) if isinstance(parlamentares, dict): From f4e5fe55d8912bdd982482089eac6c52fccefbbd Mon Sep 17 00:00:00 2001 From: Tiago Bittencourt Date: Mon, 6 Apr 2026 09:06:44 -0300 Subject: [PATCH 237/317] =?UTF-8?q?feat:=20parametriza=C3=A7=C3=A3o=20data?= =?UTF-8?q?=20recebimento=20email=20dags=20mir=20(#139)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../empenhos_tesouro_emendas_ingest_dag.py | 34 ++++++++++++++++--- ...penhos_tesouro_parlamentares_ingest_dag.py | 29 ++++++++++++++++ airflow_lappis/plugins/cliente_email.py | 28 +++++++++++---- 3 files changed, 80 insertions(+), 11 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_emendas_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_emendas_ingest_dag.py index f6f220c3..868b4851 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_emendas_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_emendas_ingest_dag.py @@ -2,6 +2,7 @@ from airflow import DAG from airflow.operators.python import PythonOperator from airflow.models import Variable +from airflow.models.param import Param from datetime import datetime, timedelta import logging import json @@ -53,7 +54,7 @@ 29: "despesas_liquidadas", 30: "despesas_pagas", 31: "restos_a_pagar_inscritos", - 32: "restos_a_pagar_pagos" + 32: "restos_a_pagar_pagos", } EMAIL_SUBJECT = "notas_de_empenhos_emendas_parlamentares" @@ -67,6 +68,17 @@ schedule_interval=get_dynamic_schedule("empenhos_tesouro_emendas_ingest_dag"), start_date=datetime(2023, 12, 1), catchup=False, + params={ + "data_referencia": Param( + default=None, + type=["string", "null"], + title="Data de Referencia", + description=( + "Data para filtrar os e-mails recebidos (formato YYYY-MM-DD). " + "Se nao informado, usa o dia atual." + ), + ) + }, tags=["MIR", "email", "empenhos", "tesouro", "emendas"], ) as dag: @@ -75,12 +87,25 @@ def process_email_data(**context: Dict[str, Any]) -> Optional[Any]: EMAIL = creds["email"] PASSWORD = creds["password"] - IMAP_SERVER = creds["imap_ser" \ - "ver"] + IMAP_SERVER = creds["imap_server"] SENDER_EMAIL = creds["sender_email"] + params = context.get("params", {}) + data_referencia = params.get("data_referencia") + + target_date = None + if data_referencia: + try: + target_date = datetime.strptime(data_referencia, "%Y-%m-%d").date() + except ValueError as exc: + raise ValueError( + "Parametro 'data_referencia' invalido. Use o formato YYYY-MM-DD." + ) from exc try: - logging.info("Iniciando o processamento dos emails...") + logging.info( + "Iniciando o processamento dos emails para a data: %s", + target_date.isoformat() if target_date else "dia atual", + ) csv_data = fetch_and_process_email( IMAP_SERVER, EMAIL, @@ -89,6 +114,7 @@ def process_email_data(**context: Dict[str, Any]) -> Optional[Any]: EMAIL_SUBJECT, COLUMN_MAPPING, skiprows=SKIPROWS, + target_date=target_date, ) if not csv_data: logging.warning( diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/empenhos_tesouro_parlamentares_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/empenhos_tesouro_parlamentares_ingest_dag.py index 2d892e6b..5b375b6e 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/empenhos_tesouro_parlamentares_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/empenhos_tesouro_parlamentares_ingest_dag.py @@ -2,6 +2,7 @@ from airflow import DAG from airflow.operators.python import PythonOperator from airflow.models import Variable +from airflow.models.param import Param from datetime import datetime, timedelta import logging import json @@ -68,6 +69,17 @@ schedule_interval=get_dynamic_schedule("empenhos_tesouro_parlamentares_ingest_dag"), start_date=datetime(2023, 12, 1), catchup=False, + params={ + "data_referencia": Param( + default=None, + type=["string", "null"], + title="Data de Referencia", + description=( + "Data para filtrar os e-mails recebidos (formato YYYY-MM-DD). " + "Se nao informado, usa o dia atual." + ), + ) + }, tags=["MIR", "email", "empenhos", "tesouro", "parlamentares"], ) as dag: @@ -92,6 +104,22 @@ def _insert_dataframe(df, db: ClientPostgresDB) -> int: def fetch_and_ingest(**context: Dict[str, Any]) -> Dict[str, int]: """Processa cada anexo e ingere imediatamente, evitando acúmulo em memória.""" creds = json.loads(Variable.get("email_credentials")) + params = context.get("params", {}) + data_referencia = params.get("data_referencia") + + target_date = None + if data_referencia: + try: + target_date = datetime.strptime(data_referencia, "%Y-%m-%d").date() + except ValueError as exc: + raise ValueError( + "Parametro 'data_referencia' invalido. Use o formato YYYY-MM-DD." + ) from exc + + logging.info( + "Buscando e-mails para a data: %s", + target_date.isoformat() if target_date else "dia atual", + ) zip_payloads: List[bytes] = fetch_email_with_zip( creds["imap_server"], @@ -99,6 +127,7 @@ def fetch_and_ingest(**context: Dict[str, Any]) -> Dict[str, int]: creds["password"], creds["sender_email"], EMAIL_SUBJECT, + target_date=target_date, ) if not zip_payloads: diff --git a/airflow_lappis/plugins/cliente_email.py b/airflow_lappis/plugins/cliente_email.py index 3f0c1b55..985bcf5b 100755 --- a/airflow_lappis/plugins/cliente_email.py +++ b/airflow_lappis/plugins/cliente_email.py @@ -6,7 +6,7 @@ from pandas.errors import EmptyDataError from imap_tools import MailBox, AND import chardet -from datetime import datetime +from datetime import datetime, date import pytz # Configuração do log @@ -61,14 +61,22 @@ def extract_csv_from_zip( def fetch_email_with_zip( - imap_server: str, email: str, password: str, sender_email: str, subject: str + imap_server: str, + email: str, + password: str, + sender_email: str, + subject: str, + target_date: Optional[date] = None, ) -> List[bytes]: - """Busca todos os e-mails do dia atual e retorna todos os anexos ZIP.""" - today = datetime.now(pytz.timezone("America/Sao_Paulo")).date() + """Busca e-mails da data alvo (ou dia atual) e retorna os anexos ZIP.""" + query_date = target_date or datetime.now(pytz.timezone("America/Sao_Paulo")).date() zip_payloads: List[bytes] = [] with MailBox(imap_server).login(email, password) as mailbox: # bulk=True: single IMAP FETCH command for all messages (avoids overquota) - for msg in mailbox.fetch(AND(date=today, from_=sender_email, subject=subject), bulk=True): + for msg in mailbox.fetch( + AND(date=query_date, from_=sender_email, subject=subject), + bulk=True, + ): for attachment in msg.attachments: if attachment.filename.lower().endswith(".zip"): zip_payloads.append(cast(bytes, attachment.payload)) @@ -83,11 +91,17 @@ def fetch_and_process_email( subject: str, column_mapping: dict, skiprows: int = 0, + target_date: Optional[date] = None, ) -> Optional[str]: - """Busca e processa e-mails do dia, extraindo CSVs de todos os ZIPs anexados.""" + """Busca e processa e-mails da data alvo (ou dia atual), extraindo CSVs.""" try: zip_payloads = fetch_email_with_zip( - imap_server, email, password, sender_email, subject + imap_server, + email, + password, + sender_email, + subject, + target_date=target_date, ) if not zip_payloads: logging.warning("Nenhum anexo ZIP encontrado.") From 5a6d7bf87239ad971e31aca234404952736a0f78 Mon Sep 17 00:00:00 2001 From: Luana Date: Mon, 6 Apr 2026 12:24:28 -0300 Subject: [PATCH 238/317] Fix dedup deputados com unique key composta --- .../dados_abertos/deputados_ingest_dag.py | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/dados_abertos/deputados_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_abertos/deputados_ingest_dag.py index b7ca23ff..24ae2bb5 100644 --- a/airflow_lappis/dags/data_ingest/dados_abertos/deputados_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_abertos/deputados_ingest_dag.py @@ -31,16 +31,24 @@ def fetch_and_store_deputados() -> None: deputados_data = api.get_all_deputados() - if deputados_data and len(deputados_data) > 0: - vistos= set() + unique_key = ["id", "siglapartido", "idlegislatura"] + + if deputados_data: + vistos = set() lista_limpa = [] for item in deputados_data: - if item["id"] not in vistos: + + if item.get("siglaPartido") is None: + item["siglaPartido"] = "Sem Partido" + + chave_unica = tuple(item.get(key) for key in unique_key) + + if chave_unica not in vistos: item["dt_ingest"] = datetime.now().isoformat() lista_limpa.append(item) - vistos.add(item["id"]) - + vistos.add(chave_unica) + deputados_data = lista_limpa logging.info( @@ -51,8 +59,8 @@ def fetch_and_store_deputados() -> None: db.insert_data( deputados_data, "deputados", - conflict_fields=["id"], - primary_key=["id"], + conflict_fields=["id", "siglapartido", "idlegislatura"], + primary_key=["id", "siglapartido", "idlegislaturax "], schema="camara_deputados", ) From b26aa77e96dd66d711fa5ab86712c7eacb840554 Mon Sep 17 00:00:00 2001 From: Wallyson Souza <120031974+devwallyson@users.noreply.github.com> Date: Mon, 6 Apr 2026 17:04:48 -0300 Subject: [PATCH 239/317] feat(sgac): adiciona ingestao de projetos via anexo CSV de email (#140) --- .../sgac/projetos_sgac_ingest_dag.py | 203 ++++++++++++++++++ airflow_lappis/plugins/cliente_email.py | 80 +++++++ 2 files changed, 283 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/sgac/projetos_sgac_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/sgac/projetos_sgac_ingest_dag.py b/airflow_lappis/dags/data_ingest/sgac/projetos_sgac_ingest_dag.py new file mode 100644 index 00000000..38e1c95f --- /dev/null +++ b/airflow_lappis/dags/data_ingest/sgac/projetos_sgac_ingest_dag.py @@ -0,0 +1,203 @@ +from typing import Dict, Any, Optional +from airflow import DAG +from airflow.operators.python import PythonOperator +from airflow.models import Variable +from datetime import datetime, timedelta +import logging +import json +from schedule_loader import get_dynamic_schedule +from cliente_email import fetch_and_process_email_csv_attachment +from cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn +import pandas as pd +import io + +# Configurações básicas da DAG +default_args = { + "owner": "Wallyson", + "depends_on_past": False, + "retries": 1, + "retry_delay": timedelta(minutes=5), +} + +COLUMN_MAPPING = { + 0: "odata_etag", + 1: "id_interno_item", + 2: "id", + 3: "titulo", + 4: "entidades_externas", + 5: "instrumento", + 6: "instrumento_id", + 7: "diretoria_responsavel", + 8: "diretoria_responsavel_id", + 9: "objeto", + 10: "data_inicio", + 11: "data_vencimento", + 12: "total_de_recursos", + 13: "numero_do_proc", + 14: "coordenador", + 15: "coordenador_tipo_odata", + 16: "coordenador_claims", + 17: "coordenador_claims_tipo_odata", + 18: "nacionalidade", + 19: "nacionalidade_tipo_odata", + 20: "nacionalidade_id", + 21: "nacionalidade_id_tipo_odata", + 22: "recursos_orcament_x00", + 23: "recursos_orcament_x0", + 24: "status", + 25: "status_id", + 26: "eixo_tematico", + 27: "eixo_tematico_tipo_odata", + 28: "eixo_tematico_id", + 29: "eixo_tematico_id_tipo_odata", + 30: "predecessores", + 31: "predecessores_tipo_odata", + 32: "predecessores_id", + 33: "predecessores_id_tipo_odata", + 34: "prioridade", + 35: "prioridade_id", + 36: "justificativa", + 37: "objetivo_s_ge", + 38: "equipe_tecnica", + 39: "equipe_tecnica_tipo_odata", + 40: "equipe_tecnica_claims", + 41: "equipe_tecnica_claims_tipo_odata", + 42: "codigo", + 43: "unidades_envolvidas", + 44: "unidades_envolvidas_tipo_odata", + 45: "unidades_envolvidas_id", + 46: "unidades_envolvidas_id_tipo_odata", + 47: "historico_observa_x0", + 48: "a_solicitacao", + 49: "a_solicitacao_tipo_odata", + 50: "a_solicitacao_id", + 51: "a_solicitacao_id_tipo_odata", + 52: "modificado", + 53: "criado", + 54: "autor", + 55: "autor_claims", + 56: "editor", + 57: "editor_claims", + 58: "identificador", + 59: "eh_pasta", + 60: "miniatura", + 61: "link", + 62: "nome", + 63: "nome_arquivo_com_extensao", + 64: "caminho", + 65: "caminho_completo", + 66: "tipo_conteudo", + 67: "tipo_conteudo_id", + 68: "possui_anexos", + 69: "numero_versao", + 70: "aprovacao", + 71: "termos_aditivos", + 72: "equipe", + 73: "percentual_concluido", + 74: "corpo", + 75: "fiscal_e_substituto", + 76: "numero_siafi", + 77: "atribuido_a", + 78: "atribuido_a_claims" +} + +EMAIL_SUBJECT = "SGAC" +SKIPROWS = 1 + +# Configurações da DAG +with DAG( + dag_id="email_projetos_sgac_ingest", + default_args=default_args, + description="Processa anexos do email de dados do SGAC e insere no db", + schedule_interval=get_dynamic_schedule("email_projetos_sgac_ingest"), + start_date=datetime(2023, 12, 1), + catchup=False, + tags=["email", "projetos", "sgac"], +) as dag: + + def process_email_data(**context: Dict[str, Any]) -> Optional[Any]: + creds = json.loads(Variable.get("email_credentials")) + + EMAIL = creds["email"] + PASSWORD = creds["password"] + IMAP_SERVER = creds["imap_server"] + SENDER_EMAIL = Variable.get("sender_email_sgac", default_var=creds["sender_email"],) + + try: + logging.info("Iniciando o processamento dos emails...") + csv_data = fetch_and_process_email_csv_attachment( + IMAP_SERVER, + EMAIL, + PASSWORD, + SENDER_EMAIL, + EMAIL_SUBJECT, + COLUMN_MAPPING, + skiprows=SKIPROWS, + ) + if not csv_data: + logging.warning("Nenhum e-mail encontrado com o assunto esperado.") + return None + + total_linhas = max(len(csv_data.splitlines()) - 1, 0) + logging.info( + "CSV processado com sucesso. Dados encontrados: %s", total_linhas + ) + return csv_data + except Exception as e: + logging.error("Erro no processamento dos emails: %s", str(e)) + raise + + def insert_data_to_db(**context: Dict[str, Any]) -> None: + """Insere no Postgres os dados retornados pela task de processamento do e-mail.""" + try: + task_instance: Any = context["ti"] + csv_data: Any = task_instance.xcom_pull(task_ids="process_emails") + + if not csv_data: + logging.warning("Nenhum dado para inserir no banco.") + return + + df = pd.read_csv(io.StringIO(csv_data)) + if df.empty: + logging.warning("CSV recebido sem registros para insercao.") + return + + data = df.to_dict(orient="records") + # Adiciona timestamp de ingestão a cada registro + for record in data: + record["dt_ingest"] = datetime.now().isoformat() + + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + + unique_key = ["id"] + + db.insert_data( + data, + "projetos_sgac", + conflict_fields=unique_key, + primary_key=unique_key, + schema="sgac", + ) + logging.info("Dados inseridos com sucesso no banco de dados.") + except Exception as e: + logging.error("Erro ao inserir dados no banco: %s", str(e)) + raise + + #tarefa 1: processar os e-mails e extrair o CSV + process_emails_task = PythonOperator( + task_id="process_emails", + python_callable=process_email_data, + provide_context=True, + ) + #tarefa 2: inserir os dados no banco de dados + insert_to_db_task = PythonOperator( + task_id="insert_to_db", + python_callable=insert_data_to_db, + provide_context=True, + ) + #Fluxo da DAG + process_emails_task >> insert_to_db_task + + \ No newline at end of file diff --git a/airflow_lappis/plugins/cliente_email.py b/airflow_lappis/plugins/cliente_email.py index 985bcf5b..2f41738c 100755 --- a/airflow_lappis/plugins/cliente_email.py +++ b/airflow_lappis/plugins/cliente_email.py @@ -83,6 +83,44 @@ def fetch_email_with_zip( return zip_payloads + +def fetch_email_with_csv( + imap_server: str, email: str, password: str, sender_email: str, subject: str +) -> List[bytes]: + """Busca todos os e-mails do dia atual e retorna anexos CSV diretos.""" + today = datetime.now(pytz.timezone("America/Sao_Paulo")).date() + csv_payloads: List[bytes] = [] + with MailBox(imap_server).login(email, password) as mailbox: + # bulk=True: single IMAP FETCH command for all messages (avoids overquota) + for msg in mailbox.fetch(AND(date=today, from_=sender_email, subject=subject), bulk=True): + for attachment in msg.attachments: + file_name = (attachment.filename or "").lower() + if file_name.endswith(".csv"): + csv_payloads.append(cast(bytes, attachment.payload)) + return csv_payloads + + +def extract_csv_from_payload( + payload: bytes, column_mapping: dict, skiprows: int = 0 +) -> Optional[pd.DataFrame]: + """Decodifica payload CSV e aplica formatação padrão.""" + if not payload.strip(): + logging.warning("Anexo CSV vazio.") + return None + + encoding = chardet.detect(payload)["encoding"] + decoded_data = payload.decode(encoding or "utf-8", errors="replace") + if not decoded_data.strip(): + logging.warning("Anexo CSV vazio apos decodificacao.") + return None + + try: + return format_csv(decoded_data, column_mapping, skiprows) + except EmptyDataError: + logging.warning("CSV sem colunas apos skiprows=%s.", skiprows) + return None + + def fetch_and_process_email( imap_server: str, email: str, @@ -128,3 +166,45 @@ def fetch_and_process_email( except Exception as e: logging.error(f"Erro ao processar e-mails: {e}") raise + + +def fetch_and_process_email_csv_attachment( + imap_server: str, + email: str, + password: str, + sender_email: str, + subject: str, + column_mapping: dict, + skiprows: int = 0, +) -> Optional[str]: + """Busca e processa e-mails do dia, extraindo CSVs anexados diretamente.""" + try: + csv_payloads = fetch_email_with_csv( + imap_server, email, password, sender_email, subject + ) + if not csv_payloads: + logging.warning("Nenhum anexo CSV encontrado.") + return None + + logging.info("Total de anexos CSV encontrados: %s", len(csv_payloads)) + + dataframes: List[pd.DataFrame] = [] + for idx, payload in enumerate(csv_payloads, start=1): + csv_data = extract_csv_from_payload(payload, column_mapping, skiprows) + if csv_data is not None: + dataframes.append(csv_data) + else: + logging.warning( + "CSV %s ignorado por nao conter dados validos.", + idx, + ) + + if dataframes: + combined_df = pd.concat(dataframes, ignore_index=True) + return combined_df.to_csv(index=False) + + logging.warning("Nenhum CSV processado.") + return None + except Exception as e: + logging.error(f"Erro ao processar e-mails com CSV direto: {e}") + raise From 5e62fb663d95b0e685cb454cbfb4d8ea8fa60e2c Mon Sep 17 00:00:00 2001 From: Luana Date: Tue, 7 Apr 2026 10:58:57 -0300 Subject: [PATCH 240/317] =?UTF-8?q?feat:=20cria=20dag=20per=C3=ADodo=20leg?= =?UTF-8?q?islatura?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mandatos_parlamentares_ingest_dag.py | 0 .../periodo_legislatura_ingest_dag.py | 65 +++++++++++++++++++ airflow_lappis/plugins/cliente_senadores.py | 37 ++++++++++- 3 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 airflow_lappis/dags/data_ingest/dados_abertos/mandatos_parlamentares_ingest_dag.py create mode 100644 airflow_lappis/dags/data_ingest/dados_abertos/periodo_legislatura_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/dados_abertos/mandatos_parlamentares_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_abertos/mandatos_parlamentares_ingest_dag.py new file mode 100644 index 00000000..e69de29b diff --git a/airflow_lappis/dags/data_ingest/dados_abertos/periodo_legislatura_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_abertos/periodo_legislatura_ingest_dag.py new file mode 100644 index 00000000..ccbdfc10 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/dados_abertos/periodo_legislatura_ingest_dag.py @@ -0,0 +1,65 @@ +import logging +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule +from postgres_helpers import get_postgres_conn +from cliente_senadores import ClienteSenadores +from cliente_postgres import ClientPostgresDB + + +@dag( + schedule_interval=get_dynamic_schedule("periodo_legislatura_ingest_dag"), + start_date=datetime(2025, 1, 1), + catchup=False, + default_args={ + "owner": "Luana", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["senado_federal", "dados_abertos", "MIR"], +) +def periodo_legislacao_ingest_dag() -> None: + """DAG para buscar e armazenar período de legislação parlamentares""" + + @task + def fetch_and_store_periodo_legislacao() -> None: + logging.info("Iniciando extração de legislaturas") + + api = ClienteSenadores() + db = ClientPostgresDB(get_postgres_conn("postgres_mir")) + + lista_legislaturas = api.get_periodo_legislacao() + + if not lista_legislaturas or not isinstance(lista_legislaturas, list): + logging.error(f"Esperava uma lista, mas recebi: {type(lista_legislaturas)}") + return + + registros_limpos = [] + + for leg in lista_legislaturas: + item_limpo = { + "id": int(leg.get("NumeroLegislatura")), + "data_inicio": leg.get("DataInicio"), + "data_fim": leg.get("DataFim"), + "data_eleicao": leg.get("DataEleicao"), + "dt_ingest": datetime.now().isoformat(), + } + registros_limpos.append(item_limpo) + + logging.info(f"Preparados {len(registros_limpos)} registros para o banco.") + + if registros_limpos: + db.insert_data( + registros_limpos, + "legislaturas", + conflict_fields=["id"], + primary_key=["id"], + schema="senado_federal", + ) + + + fetch_and_store_periodo_legislacao() + + + +periodo_legislacao_ingest_dag() \ No newline at end of file diff --git a/airflow_lappis/plugins/cliente_senadores.py b/airflow_lappis/plugins/cliente_senadores.py index c58f59d3..7edce42a 100644 --- a/airflow_lappis/plugins/cliente_senadores.py +++ b/airflow_lappis/plugins/cliente_senadores.py @@ -31,9 +31,9 @@ def get_senadores_atuais(self) -> list: ) if status == http.HTTPStatus.OK and isinstance(data, dict): - # A estrutura do JSON do Senado é: ListaParlamentarEmExercicio -> Parlamentares -> Parlamentar + # A estrutura do JSON do Senado é: ListaLegislatura -> Parlamentares -> Parlamentar try: - lista_root = data.get("ListaParlamentarEmExercicio", {}) + lista_root = data.get("ListaLegislatura", {}) parlamentares = lista_root.get("Parlamentares", {}).get("Parlamentar", []) if isinstance(parlamentares, dict): @@ -51,3 +51,36 @@ def get_senadores_atuais(self) -> list: else: logging.warning(f"[cliente_senadores.py] Failed with status: {status}") return [] + + def get_periodo_legislacao(self) -> list: + """ + Obtém o período de legislação parlamentar + """ + endpoint = "/dados/ListaLegislatura.json" + logging.info("[cliente_senadores.py] Fetching periodos legislação") + + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER + ) + + if status == http.HTTPStatus.OK and isinstance(data, dict): + # A estrutura do JSON do Senado é: ListaLegislatura -> Parlamentares -> Parlamentar + try: + lista_root = data.get("ListaLegislatura", {}) + legislaturas = lista_root.get("Legislaturas", {}).get("Legislatura", []) + + if isinstance(legislaturas, dict): + legislaturas = [legislaturas] + + logging.info( + f"[cliente_senadores.py] Successfully fetched {len(legislaturas)} legislaturas" + ) + return legislaturas + except Exception as e: + logging.error( + f"[cliente_senadores.py] Erro ao parsear JSON do Senado: {e}" + ) + return [] + else: + logging.warning(f"[cliente_senadores.py] Failed with status: {status}") + return [] From 6a53a6edcf7eea3ad21c05feb53e9126a9397ac0 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Wed, 8 Apr 2026 11:42:58 -0300 Subject: [PATCH 241/317] =?UTF-8?q?feat:=20dag=20extra=C3=A7=C3=A3o=20prog?= =?UTF-8?q?rama=C3=A7=C3=A3o=20financeira=20mir?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mir/pf_tesouro_mir_ingest_dag.py | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/pf_tesouro_mir_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/pf_tesouro_mir_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/pf_tesouro_mir_ingest_dag.py new file mode 100644 index 00000000..cd446634 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/pf_tesouro_mir_ingest_dag.py @@ -0,0 +1,157 @@ +from typing import Dict, Any +from airflow import DAG +from airflow.operators.python import PythonOperator +from airflow.models import Variable +from datetime import datetime, timedelta +import logging +import json +import pandas as pd +import io +from schedule_loader import get_dynamic_schedule +from cliente_email import fetch_and_process_email +from cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn + +# Configurações básicas da DAG +default_args = { + "owner": "Tiago", + "depends_on_past": False, + "retries": 1, + "retry_delay": timedelta(minutes=5), +} + +# Mapeamento das colunas para as programações financeiras (recebidas) +COLUMN_MAPPING = { + 0: "emissao_mes", + 1: "emissao_dia", + 2: "ug_emitente", + 3: "ug_emitente_descricao", + 4: "ug_favorecido", + 5: "ug_favorecido_descricao", + 6: "pf_evento", + 7: "pf_evento_descricao", + 8: "pf", + 9: "pf_inscricao", + 10: "pf_acao", + 11: "pf_acao_descricao", + 12: "pf_fonte_recursos", + 13: "pf_fonte_recursos_descricao", + 14: "doc_observacao", + 15: "pf_valor_linha", +} + +# Assunto do email a ser processado +EMAIL_SUBJECT = "programacoes_financeiras" +SKIPROWS = 7 + +# Configurações da DAG +with DAG( + dag_id="email_programacoes_financeiras_mir_ingest", + default_args=default_args, + description="Processa anexo consolidado de PFs por email e insere no db", + schedule_interval=get_dynamic_schedule("pf_tesouro_mir_ingest_dag"), + start_date=datetime(2023, 12, 1), + catchup=False, + tags=["email", "pfs", "tesouro", "MIR"], +) as dag: + + def process_email_data(**context: Dict[str, Any]) -> str: + """ + Função para processar o email com programações financeiras. + """ + creds = json.loads(Variable.get("email_credentials")) + + EMAIL = creds["email"] + PASSWORD = creds["password"] + IMAP_SERVER = creds["imap_server"] + SENDER_EMAIL = creds["sender_email"] + + try: + logging.info("Iniciando o processamento do email de programações financeiras") + csv_data = fetch_and_process_email( + IMAP_SERVER, + EMAIL, + PASSWORD, + SENDER_EMAIL, + EMAIL_SUBJECT, + column_mapping=COLUMN_MAPPING, + skiprows=SKIPROWS, + ) + if not csv_data: + logging.warning("Nenhum e-mail encontrado com o assunto configurado") + return "" + + logging.info("CSV de PFs processado com sucesso.") + return csv_data + except Exception as e: + logging.error( + "Erro no processamento do email de programações financeiras: %s", + str(e), + ) + raise + + def insert_data_to_db(**context: Dict[str, Any]) -> None: + """ + Função para inserir os dados no banco de dados. + Os dados processados são recuperados do XCom. + """ + try: + task_instance: Any = context["ti"] + processed_data = task_instance.xcom_pull(task_ids="process_email") + + if not processed_data: + logging.warning("Nenhum dado para inserir no banco.") + return + + df = pd.read_csv(io.StringIO(processed_data)) + data = df.to_dict(orient="records") + + # Adicionar dt_ingest a cada registro + for record in data: + record["dt_ingest"] = datetime.now().isoformat() + + postgres_conn_str = get_postgres_conn("postgres_mir") + db = ClientPostgresDB(postgres_conn_str) + + db.insert_data(data, "pf_tesouro", schema="siafi") + logging.info("Dados inseridos com sucesso no banco de dados.") + except Exception as e: + logging.error("Erro ao inserir dados no banco: %s", str(e)) + raise + + def clean_duplicates(**context: Dict[str, Any]) -> None: + """ + Task para remover duplicados da tabela 'siafi.pf_tesouro'. + """ + try: + postgres_conn_str = get_postgres_conn("postgres_mir") + db = ClientPostgresDB(postgres_conn_str) + db.remove_duplicates("pf_tesouro", COLUMN_MAPPING, schema="siafi") + + except Exception as e: + logging.error(f"Erro ao executar a limpeza de duplicados: {str(e)}") + raise + + # Tarefa 1: Processar o email com os dados consolidados + process_email_task = PythonOperator( + task_id="process_email", + python_callable=process_email_data, + provide_context=True, + ) + + # Tarefa 2: Inserir os dados no db + insert_to_db_task = PythonOperator( + task_id="insert_to_db", + python_callable=insert_data_to_db, + provide_context=True, + ) + + # Tarefa 3: Limpar duplicados no banco de dados + clean_duplicates_task = PythonOperator( + task_id="clean_duplicates", + python_callable=clean_duplicates, + provide_context=True, + ) + + # Fluxo da DAG + process_email_task >> insert_to_db_task >> clean_duplicates_task From 847f709b6076adeefc93fc5ed3345b5367db1484 Mon Sep 17 00:00:00 2001 From: alvesingrid Date: Wed, 8 Apr 2026 17:19:29 -0300 Subject: [PATCH 242/317] =?UTF-8?q?fix:atualiza=C3=A7=C3=B5es=20da=20dag?= =?UTF-8?q?=20de=20senadores=20para=20incluir=20a=20data=20de=20filia?= =?UTF-8?q?=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dados_abertos/senadores_ingest_dag.py | 88 ++++++++++++++++++- airflow_lappis/plugins/cliente_senadores.py | 26 ++++++ 2 files changed, 112 insertions(+), 2 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/dados_abertos/senadores_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_abertos/senadores_ingest_dag.py index 00b574ad..4d46f59f 100644 --- a/airflow_lappis/dags/data_ingest/dados_abertos/senadores_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_abertos/senadores_ingest_dag.py @@ -12,7 +12,7 @@ start_date=datetime(2025, 1, 1), catchup=False, default_args={ - "owner": "Cibelly", + "owner": "Ingrid", "retries": 1, "retry_delay": timedelta(minutes=5), }, @@ -49,7 +49,7 @@ def fetch_and_store_senadores() -> None: "email": info.get("EmailParlamentar"), "sigla_partido": info.get("SiglaPartidoParlamentar"), "uf": info.get("UfParlamentar"), - "id_legislatura": mandato.get("CodigoLegislatura"), + "id_legislatura": mandato.get("NumeroLegislatura"), "dt_ingest": datetime.now().isoformat(), } registros_limpos.append(senador_simplificado) @@ -66,8 +66,92 @@ def fetch_and_store_senadores() -> None: primary_key=["id"], schema="senado_federal", ) + @task + def fetch_and_store_filiacoes() -> None: + api = ClienteSenadores() + postgres_conn_str = get_postgres_conn("postgres_mir") + db = ClientPostgresDB(postgres_conn_str) + + # 1. Busca a lista base de senadores + senadores_base = api.get_senadores_por_legislatura() + registros_filiacoes = [] + + for sen in senadores_base: + info = sen.get("IdentificacaoParlamentar", {}) + cod_id = info.get("CodigoParlamentar") + nome = info.get("NomeParlamentar") + + if cod_id: + # 2. Para cada senador, busca o histórico de filiações + filiacoes = api.get_filiacoes_senador(cod_id) + if isinstance(filiacoes, dict): + filiacoes = [filiacoes] + elif not filiacoes: + logging.debug( + f"[senadores_ingest_dag.py] Nenhuma filiação encontrada para " + f"{nome} (ID: {cod_id})" + ) + continue + for f in filiacoes: + # Extrai os dados do objeto Partido aninhado + partido = f.get("Partido", {}) + sigla_partido = partido.get("SiglaPartido") or "Sigla não disponível" + nome_partido = partido.get("NomePartido") or "Nome não disponível" + ano_filiacao = f.get("AnoFiliacao") or "Data não disponível" + ano_desfiliacao = f.get("AnoDesfiliacao") + + registro = { + "id": cod_id, + "nome_parlamentar": nome, + "sigla_partido": sigla_partido, + "nome_partido": nome_partido, + "dt_filiacao": ano_filiacao, + "dt_desfiliacao": ano_desfiliacao, + "uf": info.get("UfParlamentar"), + "dt_ingest": datetime.now().isoformat(), + } + registros_filiacoes.append(registro) + logging.debug( + f"[senadores_ingest_dag.py] Filiação processada: " + f"{nome} -> {sigla_partido} ({ano_filiacao})" + ) + + logging.info( + f"[senadores_ingest_dag.py] Total de {len(registros_filiacoes)} " + f"registros de filiações coletados da API." + ) + # 3. Deduplicação baseada nas chaves únicas + registros_deduplicated = {} + for registro in registros_filiacoes: + chave_unica = ( + registro.get("id"), + registro.get("sigla_partido"), + registro.get("dt_filiacao"), + ) + if chave_unica not in registros_deduplicated: + registros_deduplicated[chave_unica] = registro + + registros_filiacoes = list(registros_deduplicated.values()) + logging.info( + f"[senadores_ingest_dag.py] Após deduplicação: {len(registros_filiacoes)} " + f"registros únicos de histórico partidário." + ) + + # 4. Inserção no banco + if registros_filiacoes: + logging.info(f"Inserindo {len(registros_filiacoes)} registros de histórico partidário.") + db.insert_data( + registros_filiacoes, + "senadores_filiacoes", # Nome da nova tabela única + conflict_fields=["id", "sigla_partido", "dt_filiacao"], + primary_key=["id", "sigla_partido", "dt_filiacao"], # Chave composta para permitir múltiplos registros do mesmo ID + schema="senado_federal", + ) fetch_and_store_senadores() + fetch_and_store_filiacoes() + + senadores_ingest_dag() diff --git a/airflow_lappis/plugins/cliente_senadores.py b/airflow_lappis/plugins/cliente_senadores.py index 8fcc2b4b..e6f060c6 100644 --- a/airflow_lappis/plugins/cliente_senadores.py +++ b/airflow_lappis/plugins/cliente_senadores.py @@ -51,3 +51,29 @@ def get_senadores_por_legislatura(self) -> list: else: logging.warning(f"[cliente_senadores.py] Failed with status: {status}") return [] + + def get_filiacoes_senador(self, codigo_parlamentar: int) -> list: + """ + Obtém o histórico de filiações partidárias de um senador específico. + """ + endpoint = f"/senador/{codigo_parlamentar}/filiacoes" + logging.info(f"[cliente_senadores.py] Fetching filiações para o senador: {codigo_parlamentar}") + + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER + ) + + if status == http.HTTPStatus.OK and isinstance(data, dict): + try: + # Estrutura: FiliacaoPartidaria -> Parlamentar -> Filiacoes -> Filiacao + filiacoes_root = data.get("FiliacaoParlamentar", {}).get("Parlamentar", {}) + filiacoes = filiacoes_root.get("Filiacoes", {}).get("Filiacao", []) + + if isinstance(filiacoes, dict): + filiacoes = [filiacoes] + + return filiacoes + except Exception as e: + logging.error(f"[cliente_senadores.py] Erro ao parsear filiações: {e}") + return [] + return [] From a86bb8978d792d840e8884a68115ac7c28e495b0 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Wed, 8 Apr 2026 17:43:57 -0300 Subject: [PATCH 243/317] =?UTF-8?q?feat:=20dag=20extra=C3=A7=C3=A3o=20prog?= =?UTF-8?q?rama=C3=A7=C3=A3o=20a=C3=A7=C3=A3o=20por=20ptres?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mir/programacao_acao_ptres_ingest_dag.py | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/programacao_acao_ptres_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/programacao_acao_ptres_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/programacao_acao_ptres_ingest_dag.py new file mode 100644 index 00000000..cd0ced74 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/programacao_acao_ptres_ingest_dag.py @@ -0,0 +1,158 @@ +from typing import Dict, Any +from airflow import DAG +from airflow.operators.python import PythonOperator +from airflow.models import Variable +from datetime import datetime, timedelta +import logging +import json +import pandas as pd +import io +from schedule_loader import get_dynamic_schedule +from cliente_email import fetch_and_process_email +from cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn + +# Configurações básicas da DAG +default_args = { + "owner": "Tiago", + "depends_on_past": False, + "retries": 1, + "retry_delay": timedelta(minutes=5), +} + +# Mapeamento das colunas para as programações financeiras (recebidas) +COLUMN_MAPPING = { + 0: "programa_governo", + 1: "programa_governo_descricao", + 2: "plano_orcamentario", + 3: "plano_orcamentario_descricao_1", + 4: "plano_orcamentario_descricao_2", + 5: "plano_orcamentario_descricao_3", + 6: "plano_orcamentario_descricao_4", + 7: "plano_orcamentario_descricao_5", + 8: "plano_orcamentario_descricao_6", + 9: "acao_governo", + 10: "acao_governo_descricao", + 11: "ptres", + 12: "natureza_despesa", + 13: "natureza_despesa_descricao", + 14: "dotacao_inicial", + 15: "dotacao_suplementar", + 16: "dotacao_atualizada", +} + +# Assunto do email a ser processado +EMAIL_SUBJECT = "programacao_acao_por_PTRES" +SKIPROWS = 5 + +# Configurações da DAG +with DAG( + dag_id="email_programacao_acao_por_PTRES_ingest", + default_args=default_args, + description="Processa anexo consolidado de programações de ação por PTRES por email e insere no db", + schedule_interval=get_dynamic_schedule("programacao_acao_ptres_ingest_dag"), + start_date=datetime(2023, 12, 1), + catchup=False, + tags=["email", "ptres", "tesouro", "MIR"], +) as dag: + + def process_email_data(**context: Dict[str, Any]) -> str: + """ + Função para processar o email com programações financeiras. + """ + creds = json.loads(Variable.get("email_credentials")) + + EMAIL = creds["email"] + PASSWORD = creds["password"] + IMAP_SERVER = creds["imap_server"] + SENDER_EMAIL = creds["sender_email"] + + try: + logging.info("Iniciando o processamento do email de programações financeiras") + csv_data = fetch_and_process_email( + IMAP_SERVER, + EMAIL, + PASSWORD, + SENDER_EMAIL, + EMAIL_SUBJECT, + column_mapping=COLUMN_MAPPING, + skiprows=SKIPROWS, + ) + if not csv_data: + logging.warning("Nenhum e-mail encontrado com o assunto configurado") + return "" + + logging.info("CSV de programações de ação por PTRES processado com sucesso.") + return csv_data + except Exception as e: + logging.error( + "Erro no processamento do email de programações de ação por PTRES: %s", + str(e), + ) + raise + + def insert_data_to_db(**context: Dict[str, Any]) -> None: + """ + Função para inserir os dados no banco de dados. + Os dados processados são recuperados do XCom. + """ + try: + task_instance: Any = context["ti"] + processed_data = task_instance.xcom_pull(task_ids="process_email") + + if not processed_data: + logging.warning("Nenhum dado para inserir no banco.") + return + + df = pd.read_csv(io.StringIO(processed_data)) + data = df.to_dict(orient="records") + + # Adicionar dt_ingest a cada registro + for record in data: + record["dt_ingest"] = datetime.now().isoformat() + + postgres_conn_str = get_postgres_conn("postgres_mir") + db = ClientPostgresDB(postgres_conn_str) + + db.insert_data(data, "programacao_acao_ptres", schema="siafi") + logging.info("Dados inseridos com sucesso no banco de dados.") + except Exception as e: + logging.error("Erro ao inserir dados no banco: %s", str(e)) + raise + + def clean_duplicates(**context: Dict[str, Any]) -> None: + """ + Task para remover duplicados da tabela 'siafi.programacao_acao_ptres'. + """ + try: + postgres_conn_str = get_postgres_conn("postgres_mir") + db = ClientPostgresDB(postgres_conn_str) + db.remove_duplicates("programacao_acao_ptres", COLUMN_MAPPING, schema="siafi") + + except Exception as e: + logging.error(f"Erro ao executar a limpeza de duplicados: {str(e)}") + raise + + # Tarefa 1: Processar o email com os dados consolidados + process_email_task = PythonOperator( + task_id="process_email", + python_callable=process_email_data, + provide_context=True, + ) + + # Tarefa 2: Inserir os dados no db + insert_to_db_task = PythonOperator( + task_id="insert_to_db", + python_callable=insert_data_to_db, + provide_context=True, + ) + + # Tarefa 3: Limpar duplicados no banco de dados + clean_duplicates_task = PythonOperator( + task_id="clean_duplicates", + python_callable=clean_duplicates, + provide_context=True, + ) + + # Fluxo da DAG + process_email_task >> insert_to_db_task >> clean_duplicates_task From 8e1c76ef49075721fe3530112d62536899a5762d Mon Sep 17 00:00:00 2001 From: Mateushqms Date: Wed, 8 Apr 2026 22:04:14 -0300 Subject: [PATCH 244/317] feat: criacao da dag de extracao das notas de credito dos teds mir --- .../mir/nc_tesouro_ingest_2025_mir_dag.py | 233 ++++++++++++++++++ .../mir/nc_tesouro_ingest_2026_mir_dag.py | 143 +++++++++++ 2 files changed, 376 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/nc_tesouro_ingest_2025_mir_dag.py create mode 100644 airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/nc_tesouro_ingest_2026_mir_dag.py diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/nc_tesouro_ingest_2025_mir_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/nc_tesouro_ingest_2025_mir_dag.py new file mode 100644 index 00000000..97387064 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/nc_tesouro_ingest_2025_mir_dag.py @@ -0,0 +1,233 @@ +from typing import Dict, Any, cast +from airflow import DAG +from airflow.operators.python import PythonOperator +from airflow.models import Variable +from datetime import datetime, timedelta +import logging +import json +import pandas as pd +import io +from schedule_loader import get_dynamic_schedule +from cliente_email import fetch_and_process_email +from cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn + +default_args = { + "owner": "Mateus", + "depends_on_past": False, + "retries": 1, + "retry_delay": timedelta(minutes=5), +} + +COLUMN_MAPPING = { + 0: "programa_governo", + 1: "programa_governo_descricao", + 2: "acao_governo", + 3: "acao_governo_descricao", + 4: "nc", + 5: "nc_transferencia", + 6: "nc_fonte_recursos", + 7: "nc_fonte_recursos_descricao", + 8: "ptres", + 9: "nc_evento", + 10: "nc_evento_descricao", + 11: "nc_ug_responsavel", + 12: "nc_ug_responsavel_descricao", + 13: "nc_natureza_despesa", + 14: "nc_natureza_despesa_descricao", + 15: "nc_plano_interno", + 16: "nc_plano_interno_descricao1", + 17: "nc_plano_interno_descricao2", + 18: "favorecido_doc", + 19: "favorecido_doc_descricao", + 20: "favorecido_municipio", + 21: "favorecido_municipio_descricao", + 22: "nc_valor_linha", + 23: "movimento_liquido_moeda_origem", +} + +# Configurações dos emails +EMAIL_CONFIGS = { + "enviadas": { + "subject": "notas_credito_enviadas_devolvidas_ate_2025", + "column_mapping": COLUMN_MAPPING, + "skiprows": 6, + }, + "recebidas": { + "subject": " recebidas ", + "column_mapping": None, + "skiprows": 9, + }, +} +expected_columns = list(COLUMN_MAPPING.values()) +with DAG( + dag_id="email_notas_credito_ingest_blablabla", + default_args=default_args, + description="Processa anexos das NCs vindo de dois emails, formata e insere no db", + schedule_interval=get_dynamic_schedule("nc_tesouro_ingest_dag_blablabla"), + start_date=datetime(2023, 12, 1), + catchup=False, + tags=["email", "ncs", "tesouro", "MIR"], +) as dag: + + def process_email_data(email_type: str, **context: Dict[str, Any]) -> pd.DataFrame: + """ + Função genérica para processar emails de notas de crédito. + """ + config = EMAIL_CONFIGS[email_type] + creds_data = json.loads(Variable.get("email_credentials")) + creds = cast(Dict[str, str], creds_data) + config = cast(Dict[str, Any], config) + + try: + logging.info(f"Iniciando o processamento das NCs {email_type}") + csv_data = fetch_and_process_email( + creds["imap_server"], + creds["email"], + creds["password"], + creds["sender_email"], + config["subject"], + config["column_mapping"], + skiprows=config["skiprows"], + ) + + if not csv_data: + logging.warning(f"Nenhum e-mail encontrado para NCs {email_type}") + return pd.DataFrame() + + df = pd.read_csv(io.StringIO(csv_data)) + + # Se não tem mapeamento de colunas (recebidas), aplicar o mapeamento padrão + if config["column_mapping"] is None and not df.empty: + expected_columns = list(COLUMN_MAPPING.values()) + if len(df.columns) == len(expected_columns): + df.columns = pd.Index(expected_columns) + else: + logging.warning( + f"N coluna incompatível:{len(expected_columns)},{len(df.columns)}" + ) + + logging.info( + f"CSV de NCs {email_type} processado com sucesso: {len(df)} registros" + ) + return df + + except Exception as e: + logging.error( + f"Erro no processamento dos emails de NCs {email_type}: {str(e)}" + ) + raise + + def process_email_data_enviadas(**context: Dict[str, Any]) -> pd.DataFrame: + """Wrapper para processar emails enviadas.""" + return process_email_data("enviadas", **context) + + def process_email_data_recebidas(**context: Dict[str, Any]) -> pd.DataFrame: + """Wrapper para processar emails recebidas.""" + return process_email_data("recebidas", **context) + + def combine_data(**context: Dict[str, Any]) -> pd.DataFrame: + """ + Função para combinar os dados dos dois emails. + """ + try: + task_instance: Any = context["ti"] + df_enviadas = cast( + pd.DataFrame, task_instance.xcom_pull(task_ids="process_emails_enviadas") + ) + df_recebidas = cast( + pd.DataFrame, task_instance.xcom_pull(task_ids="process_emails_recebidas") + ) + + dfs = [ + df + for df in [df_enviadas, df_recebidas] + if df is not None and not df.empty + ] + + if not dfs: + logging.warning("Nenhum dado foi encontrado para combinar.") + return pd.DataFrame() + + combined_df = pd.concat(dfs, ignore_index=True) + combined_df["dt_ingest"] = datetime.now().isoformat() + + logging.info(f"Dados combinados: {len(combined_df)} registros no total.") + return combined_df + + except Exception as e: + logging.error(f"Erro ao combinar os dados: {str(e)}") + raise + + def insert_data_to_db(**context: Dict[str, Any]) -> None: + """ + Função para inserir os dados no banco de dados. + """ + try: + task_instance: Any = context["ti"] + combined_df = task_instance.xcom_pull(task_ids="combine_data") + + if combined_df is None or combined_df.empty: + logging.warning("Nenhum dado para inserir no banco.") + return + + data = combined_df.to_dict(orient="records") + + postgres_conn_str = get_postgres_conn('postgres_mir') + db = ClientPostgresDB(postgres_conn_str) + + db.insert_data(data, "nc_tesouro", schema="siafi") + logging.info("Dados inseridos com sucesso no banco de dados.") + except Exception as e: + logging.error("Erro ao inserir dados no banco: %s", str(e)) + raise + + def clean_duplicates(**context: Dict[str, Any]) -> None: + """ + Task para remover duplicados da tabela 'siafi.pf_tesouro'. + """ + try: + postgres_conn_str = get_postgres_conn('postgres_mir') + db = ClientPostgresDB(postgres_conn_str) + db.remove_duplicates("nc_tesouro", COLUMN_MAPPING, schema="siafi") + + except Exception as e: + logging.error(f"Erro ao executar a limpeza de duplicados: {str(e)}") + raise + + process_emails_enviadas_task = PythonOperator( + task_id="process_emails_enviadas", + python_callable=process_email_data_enviadas, + provide_context=True, + ) + + process_emails_recebidas_task = PythonOperator( + task_id="process_emails_recebidas", + python_callable=process_email_data_recebidas, + provide_context=True, + ) + + combine_data_task = PythonOperator( + task_id="combine_data", + python_callable=combine_data, + provide_context=True, + ) + + insert_to_db_task = PythonOperator( + task_id="insert_to_db", + python_callable=insert_data_to_db, + provide_context=True, + ) + + clean_duplicates_task = PythonOperator( + task_id="clean_duplicates", + python_callable=clean_duplicates, + provide_context=True, + ) + + ( + [process_emails_enviadas_task, process_emails_recebidas_task] + >> combine_data_task + >> insert_to_db_task + >> clean_duplicates_task + ) diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/nc_tesouro_ingest_2026_mir_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/nc_tesouro_ingest_2026_mir_dag.py new file mode 100644 index 00000000..881e428f --- /dev/null +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/nc_tesouro_ingest_2026_mir_dag.py @@ -0,0 +1,143 @@ +from typing import Dict, Any, Optional +from airflow import DAG +from airflow.operators.python import PythonOperator +from airflow.models import Variable +from datetime import datetime, timedelta +import logging +import json +import pandas as pd +import io +from schedule_loader import get_dynamic_schedule +from cliente_email import fetch_and_process_email +from cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn +from functools import partial + +default_args = { + "owner": "Mateus", + "depends_on_past": False, + "retries": 1, + "retry_delay": timedelta(minutes=5), +} + +pd.read_csv = partial(pd.read_csv, sep='\t', on_bad_lines='skip') + +COLUMN_MAPPING_NC = { + 0: "emissao_dia", + 1: "nc", + 2: "emitente_codigo", + 3: "emitente_nome", + 4: "ptres", + 5: "fonte_codigo", + 6: "fonte_nome", + 7: "gnd_codigo", + 8: "gnd_nome", + 9: "pi_codigo", + 10: "pi_nome", + 11: "descricao", + 12: "ugr_codigo", + 13: "ugr_nome", + 14: "tipo_nc", + 15: "nc_item_detalhamento", + 16: "favorecido_codigo", + 17: "favorecido_nome", + 18: "ro", + 19: "nc_transferencia", + 20: "dc", + 21: "item_total", + 22: "total_lista", + 23: "valor_celula", + 24: "esfera_orcamentaria_codigo", + 25: "esfera_orcamentaria_nome", + 26: "emissao_ano", + 27: "emissao_mes", +} + +EMAIL_SUBJECT = "notas_credito_enviadas_devolvidas_a_partir_2026" +SKIPROWS = 3 + +with DAG( + dag_id="email_notas_credito_ingest_2026", + default_args=default_args, + description="Processa notas de crédito vindas do email sem filtro de data", + schedule_interval=get_dynamic_schedule("notas_credito_email_ingest_dag_2026"), + start_date=datetime(2024, 1, 1), + catchup=False, + tags=["MIR", "email", "notas_credito"], +) as dag: + + def process_email_data(**context: Dict[str, Any]) -> Optional[Any]: + creds = json.loads(Variable.get("email_credentials")) + + try: + logging.info("Iniciando coleta de emails para o assunto: %s", EMAIL_SUBJECT) + csv_data = fetch_and_process_email( + creds["imap_server"], + creds["email"], + creds["password"], + creds["sender_email"], + EMAIL_SUBJECT, + COLUMN_MAPPING_NC, + skiprows=SKIPROWS, + ) + + if not csv_data: + logging.warning("Nenhum CSV extraído.") + return None + + return csv_data + except Exception as e: + logging.error("Erro no processamento: %s", str(e)) + raise + + def insert_data_to_db(**context: Dict[str, Any]) -> None: + try: + ti = context["ti"] + csv_data = ti.xcom_pull(task_ids="process_emails") + + if not csv_data: + return + + df = pd.read_csv(io.StringIO(csv_data), sep=',') + data = df.to_dict(orient="records") + + for record in data: + record["dt_ingest"] = datetime.now().isoformat() + + postgres_conn_str = get_postgres_conn("postgres_mir") + db = ClientPostgresDB(postgres_conn_str) + + unique_key = [ + "nc", + "emissao_dia", + "emissao_mes", + "emissao_ano", + "ptres", + "ugr_codigo", + "valor_celula", + "dc", + ] + + db.insert_data( + data, + "empenhos_tesouro_notas_credito", + conflict_fields=unique_key, + primary_key=unique_key, + schema="siafi", + ) + logging.info("Carga finalizada com sucesso.") + except Exception as e: + logging.error("Erro na inserção: %s", str(e)) + raise + + process_emails_task = PythonOperator( + task_id="process_emails", + python_callable=process_email_data, + ) + + insert_to_db_task = PythonOperator( + task_id="insert_to_db", + python_callable=insert_data_to_db, + ) + + process_emails_task >> insert_to_db_task \ No newline at end of file From 395649de3bcc0f681e90492271b72617793e9ecd Mon Sep 17 00:00:00 2001 From: Mateushqms Date: Thu, 9 Apr 2026 09:56:56 -0300 Subject: [PATCH 245/317] refactor: correcao dos nomes dos emails e nomes das dags --- .../mir/nc_tesouro_ingest_2025_mir_dag.py | 19 +++++++++---------- .../mir/nc_tesouro_ingest_2026_mir_dag.py | 7 +++---- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/nc_tesouro_ingest_2025_mir_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/nc_tesouro_ingest_2025_mir_dag.py index 97387064..91a5fb76 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/nc_tesouro_ingest_2025_mir_dag.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/nc_tesouro_ingest_2025_mir_dag.py @@ -25,7 +25,7 @@ 2: "acao_governo", 3: "acao_governo_descricao", 4: "nc", - 5: "nc_transferencia", + 5: "nc_transferencia", 6: "nc_fonte_recursos", 7: "nc_fonte_recursos_descricao", 8: "ptres", @@ -51,23 +51,22 @@ "enviadas": { "subject": "notas_credito_enviadas_devolvidas_ate_2025", "column_mapping": COLUMN_MAPPING, - "skiprows": 6, + "skiprows": 3, }, "recebidas": { - "subject": " recebidas ", + "subject": "notas_credito_recebidas_ate_2025", "column_mapping": None, - "skiprows": 9, + "skiprows": 6, }, } expected_columns = list(COLUMN_MAPPING.values()) with DAG( - dag_id="email_notas_credito_ingest_blablabla", + dag_id="email_notas_credito_ingest_mir_ate_2025", default_args=default_args, - description="Processa anexos das NCs vindo de dois emails, formata e insere no db", - schedule_interval=get_dynamic_schedule("nc_tesouro_ingest_dag_blablabla"), + schedule_interval=get_dynamic_schedule("email_notas_credito_ingest_mir_ate_2025"), start_date=datetime(2023, 12, 1), catchup=False, - tags=["email", "ncs", "tesouro", "MIR"], + tags=["MIR", "SIAFI", "notas_credito"], ) as dag: def process_email_data(email_type: str, **context: Dict[str, Any]) -> pd.DataFrame: @@ -176,7 +175,7 @@ def insert_data_to_db(**context: Dict[str, Any]) -> None: postgres_conn_str = get_postgres_conn('postgres_mir') db = ClientPostgresDB(postgres_conn_str) - db.insert_data(data, "nc_tesouro", schema="siafi") + db.insert_data(data, "nc_tesouro_pre_2026", schema="siafi") logging.info("Dados inseridos com sucesso no banco de dados.") except Exception as e: logging.error("Erro ao inserir dados no banco: %s", str(e)) @@ -189,7 +188,7 @@ def clean_duplicates(**context: Dict[str, Any]) -> None: try: postgres_conn_str = get_postgres_conn('postgres_mir') db = ClientPostgresDB(postgres_conn_str) - db.remove_duplicates("nc_tesouro", COLUMN_MAPPING, schema="siafi") + db.remove_duplicates("nc_tesouro_pre_2026", COLUMN_MAPPING, schema="siafi") except Exception as e: logging.error(f"Erro ao executar a limpeza de duplicados: {str(e)}") diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/nc_tesouro_ingest_2026_mir_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/nc_tesouro_ingest_2026_mir_dag.py index 881e428f..0a42e541 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/nc_tesouro_ingest_2026_mir_dag.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/nc_tesouro_ingest_2026_mir_dag.py @@ -57,10 +57,9 @@ SKIPROWS = 3 with DAG( - dag_id="email_notas_credito_ingest_2026", + dag_id="email_notas_credito_ingest_mir_pos_2026", default_args=default_args, - description="Processa notas de crédito vindas do email sem filtro de data", - schedule_interval=get_dynamic_schedule("notas_credito_email_ingest_dag_2026"), + schedule_interval=get_dynamic_schedule("email_notas_credito_ingest_mir_post_2026"), start_date=datetime(2024, 1, 1), catchup=False, tags=["MIR", "email", "notas_credito"], @@ -120,7 +119,7 @@ def insert_data_to_db(**context: Dict[str, Any]) -> None: db.insert_data( data, - "empenhos_tesouro_notas_credito", + "nc_tesouro_pos__2026", conflict_fields=unique_key, primary_key=unique_key, schema="siafi", From 503914f707ce72bb9a01c5a67648613162bef881 Mon Sep 17 00:00:00 2001 From: Luana Date: Thu, 9 Apr 2026 14:23:56 -0300 Subject: [PATCH 246/317] fix: typo na dag de deputados --- .../dags/data_ingest/dados_abertos/deputados_ingest_dag.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airflow_lappis/dags/data_ingest/dados_abertos/deputados_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_abertos/deputados_ingest_dag.py index 24ae2bb5..01ee5e6b 100644 --- a/airflow_lappis/dags/data_ingest/dados_abertos/deputados_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_abertos/deputados_ingest_dag.py @@ -60,7 +60,7 @@ def fetch_and_store_deputados() -> None: deputados_data, "deputados", conflict_fields=["id", "siglapartido", "idlegislatura"], - primary_key=["id", "siglapartido", "idlegislaturax "], + primary_key=["id", "siglapartido", "idlegislatura"], schema="camara_deputados", ) From 8844508a71e6dd1a4be3a3671dbef6fc4f0badd5 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Thu, 9 Apr 2026 16:57:41 -0300 Subject: [PATCH 247/317] =?UTF-8?q?fix:=20ingest=C3=B5es=20colunas=20RPs?= =?UTF-8?q?=20opcionais?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...penhos_tesouro_parlamentares_ingest_dag.py | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/empenhos_tesouro_parlamentares_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/empenhos_tesouro_parlamentares_ingest_dag.py index 5b375b6e..749f0325 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/empenhos_tesouro_parlamentares_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/empenhos_tesouro_parlamentares_ingest_dag.py @@ -60,6 +60,9 @@ EMAIL_SUBJECT = "notas_de_empenho_ano_atual" SKIPROWS = 8 +TABLE_NAME = "empenhos_tesouro_parlamentares" +SCHEMA_NAME = "siafi" +OPTIONAL_COLUMNS = ["restos_a_pagar_inscritos", "restos_a_pagar_pagos"] # Configurações da DAG with DAG( @@ -86,18 +89,41 @@ def _get_db_client() -> ClientPostgresDB: return ClientPostgresDB(get_postgres_conn("postgres_mir")) + def _table_exists(db: ClientPostgresDB) -> bool: + result = db.execute_query( + f"SELECT to_regclass('{SCHEMA_NAME}.{TABLE_NAME}') IS NOT NULL;" + ) + return bool(result and result[0][0]) + + def _normalize_optional_columns(df): + for column in OPTIONAL_COLUMNS: + if column not in df.columns: + df[column] = None + return df + + def _ensure_optional_columns_in_table(db: ClientPostgresDB) -> None: + db.alter_table( + {column: None for column in OPTIONAL_COLUMNS}, + TABLE_NAME, + schema=SCHEMA_NAME, + ) + def _insert_dataframe(df, db: ClientPostgresDB) -> int: + df = _normalize_optional_columns(df) df = df[df["ne_ccor_ano_emissao"].astype(str).str.startswith("20")] records = df.to_dict(orient="records") for r in records: r["dt_ingest"] = datetime.now().isoformat() + if _table_exists(db): + _ensure_optional_columns_in_table(db) + db.insert_data( records, - "empenhos_tesouro_parlamentares", + TABLE_NAME, conflict_fields=UNIQUE_KEY, primary_key=UNIQUE_KEY, - schema="siafi", + schema=SCHEMA_NAME, ) return len(records) From b2497a416b2596ef2e6e395c64e42932a7174409 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Thu, 9 Apr 2026 16:59:47 -0300 Subject: [PATCH 248/317] chore: renomeia tabela e dag apropriadamente --- .../mir/empenhos_tesouro_parlamentares_ingest_dag.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/empenhos_tesouro_parlamentares_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/empenhos_tesouro_parlamentares_ingest_dag.py index 749f0325..607f2622 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/empenhos_tesouro_parlamentares_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/empenhos_tesouro_parlamentares_ingest_dag.py @@ -60,13 +60,13 @@ EMAIL_SUBJECT = "notas_de_empenho_ano_atual" SKIPROWS = 8 -TABLE_NAME = "empenhos_tesouro_parlamentares" +TABLE_NAME = "ne_tesouro" SCHEMA_NAME = "siafi" OPTIONAL_COLUMNS = ["restos_a_pagar_inscritos", "restos_a_pagar_pagos"] # Configurações da DAG with DAG( - dag_id="email_tesouro_parlamentares_ingest", + dag_id="email_tesouro_teds_notas_empenhadas_ingest_dag", default_args=default_args, description="Processa anexos dos empenhos vindo do email, formata e insere no db", schedule_interval=get_dynamic_schedule("empenhos_tesouro_parlamentares_ingest_dag"), @@ -83,7 +83,7 @@ ), ) }, - tags=["MIR", "email", "empenhos", "tesouro", "parlamentares"], + tags=["MIR", "email", "empenhos", "tesouro"], ) as dag: def _get_db_client() -> ClientPostgresDB: From d16a54a6706001df1915ff883152c4055d65883a Mon Sep 17 00:00:00 2001 From: Mateushqms Date: Fri, 10 Apr 2026 13:40:12 -0300 Subject: [PATCH 249/317] feat: camada bronze notas credito dos teds --- .../bronze/nc_tesouro_mir.sql | 99 +++++++++++++++ .../empenhos_ted_dbt/bronze/schema.yaml | 113 ++++++++++++++++++ .../dags/dbt/mir/models/sources.yml | 4 +- 3 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/nc_tesouro_mir.sql diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/nc_tesouro_mir.sql b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/nc_tesouro_mir.sql new file mode 100644 index 00000000..ba71d216 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/nc_tesouro_mir.sql @@ -0,0 +1,99 @@ +{{ config(materialized='table')}} + +with + + notas_credito_pre as ( + select + nc, + ptres, + nc_transferencia, + nc_ug_responsavel as ugr_codigo, + nc_ug_responsavel_descricao as ugr_nome, + nc_fonte_recursos as fonte_codigo, + nc_fonte_recursos_descricao as fonte_nome, + nc_plano_interno as pi_codigo, + nc_plano_interno_descricao1 as pi_nome, + favorecido_doc as favorecido_codigo, + favorecido_doc_descricao as favorecido_nome, + {{parse_financial_value("nc_valor_linha")}} as valor_celula, + (dt_ingest || '-03:00')::timestamptz as dt_ingest, + programa_governo, + programa_governo_descricao, + acao_governo, + acao_governo_descricao, + nc_evento, + nc_evento_descricao, + nc_natureza_despesa, + nc_natureza_despesa_descricao, + nc_plano_interno_descricao2, + favorecido_municipio, + favorecido_municipio_descricao, + {{parse_financial_value("movimento_liquido_moeda_origem")}} as movimento_liquido_moeda_origem, + cast(null as date) as emissao_dia, + cast(null as varchar) as emissao_mes, + cast(null as varchar) as emissao_ano, + cast(null as varchar) as dc, + cast(null as varchar) as emitente_codigo, + cast(null as varchar) as emitente_nome, + cast(null as varchar) as gnd_codigo, + cast(null as varchar) as gnd_nome, + cast(null as varchar) as descricao, + cast(null as varchar) as tipo_nc, + cast(null as varchar) as nc_item_detalhamento, + cast(null as varchar) as ro, + 00::numeric(15,2) as item_total, + 00::numeric(15,2) as total_lista, + cast(null as varchar) as esfera_orcamentaria_codigo, + cast(null as varchar) as esfera_orcamentaria_nome + from {{ source("siafi", "nc_tesouro_pre_2026") }} + ), + notas_credito_pos as ( + select + nc, + ptres, + nc_transferencia, + ugr_codigo, + ugr_nome, + fonte_codigo, + fonte_nome, + pi_codigo, + pi_nome, + favorecido_codigo, + favorecido_nome, + {{parse_financial_value("valor_celula")}} as valor_celula, + (dt_ingest || '-03:00')::timestamptz as dt_ingest, + cast(null as varchar) as programa_governo, + cast(null as varchar) as programa_governo_descricao, + cast(null as varchar) as acao_governo, + cast(null as varchar) as acao_governo_descricao, + cast(null as varchar) as nc_evento, + cast(null as varchar) as nc_evento_descricao, + cast(null as varchar) as nc_natureza_despesa, + cast(null as varchar) as nc_natureza_despesa_descricao, + cast(null as varchar) as nc_plano_interno_descricao2, + cast(null as varchar) as favorecido_municipio, + cast(null as varchar) as favorecido_municipio_descricao, + 00::numeric(15,2) as movimento_liquido_moeda_origem, + to_date(emissao_dia, 'DD/MM/YYYY') as emissao_dia, + emissao_mes, + emissao_ano, + dc, + emitente_codigo, + emitente_nome, + gnd_codigo, + gnd_nome, + descricao, + tipo_nc, + nc_item_detalhamento, + ro, + {{parse_financial_value("item_total")}} as item_total, + {{parse_financial_value("total_lista")}} as total_lista, + esfera_orcamentaria_codigo, + esfera_orcamentaria_nome + + from {{ source("siafi", "nc_tesouro_pos__2026") }} + ) + +select * from notas_credito_pre +union all +select * from notas_credito_pos \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/schema.yaml b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/schema.yaml index ca6fa6a5..339761d4 100644 --- a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/schema.yaml +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/schema.yaml @@ -99,4 +99,117 @@ models: - verificacao_tipagem: nome_tabela: 'mir.empenhos_tesouro_parlamentares' nome_coluna: 'restos_a_pagar_inscritos' + tipo_esperado: 'numeric' + + - name: nc_tesouro_mir + description: > + Modelo da camada bronze que consolida os dados brutos de Notas de Crédito do Tesouro Gerencial (SIAFI), + abrangendo os períodos anteriores e posteriores a 2026. + Realiza a unificação (UNION ALL) das bases históricas e atuais, preenchendo com valores nulos + (ou zero para campos financeiros) as colunas inexistentes em cada tabela original para garantir a integridade do schema. + O processamento inclui: + - Casting de tipos para texto e numérico; + - Limpeza de caracteres especiais em números de processo via Regex; + - Padronização de nomes de favorecidos para caixa alta; + - Conversão de valores financeiros para **numeric(15,2)** via macro; + - Filtro de segurança para processar apenas anos de emissão válidos (4 dígitos). + meta: + tags: + - bronze + columns: + - name: nc + description: "Número identificador da Nota de Crédito (NC)." + - name: ptres + description: "Programa de Trabalho Resumido (PTRES)." + - name: nc_transferencia + description: "Informações sobre transferência da Nota de Crédito." + - name: ugr_codigo + description: "Código da Unidade Gestora Responsável." + - name: ugr_nome + description: "Nome da Unidade Gestora Responsável." + - name: fonte_codigo + description: "Código da fonte de recursos." + - name: fonte_nome + description: "Descrição da fonte de recursos." + - name: pi_codigo + description: "Código do Plano Interno." + - name: pi_nome + description: "Descrição do Plano Interno." + - name: favorecido_codigo + description: "Identificador (CPF/CNPJ) do favorecido." + - name: favorecido_nome + description: "Nome ou razão social do favorecido." + - name: valor_celula + description: "Valor financeiro consolidado da transação." + - name: dt_ingest + description: "Timestamp da ingestão dos dados (UTC-3)." + - name: programa_governo + description: "Código identificador do programa de governo." + - name: programa_governo_descricao + description: "Descrição detalhada do programa de governo." + - name: acao_governo + description: "Código da ação orçamentária." + - name: acao_governo_descricao + description: "Descrição da ação orçamentária." + - name: nc_evento + description: "Código do evento contábil." + - name: nc_evento_descricao + description: "Descrição do evento contábil." + - name: nc_natureza_despesa + description: "Código da natureza de despesa." + - name: nc_natureza_despesa_descricao + description: "Descrição da natureza de despesa." + - name: nc_plano_interno_descricao2 + description: "Segunda descrição detalhada do Plano Interno." + - name: favorecido_municipio + description: "Código do município do favorecido." + - name: favorecido_municipio_descricao + description: "Nome do município do favorecido." + - name: movimento_liquido_moeda_origem + description: "Valor do movimento líquido na moeda de origem." + - name: emissao_dia + description: "Dia da emissão da nota de crédito." + - name: emissao_mes + description: "Mês de emissão da nota de crédito." + - name: emissao_ano + description: "Ano de emissão da nota de crédito." + - name: dc + description: "Indicador de Débito ou Crédito (D/C)." + - name: emitente_codigo + description: "Código da Unidade Gestora emitente." + - name: emitente_nome + description: "Nome da Unidade Gestora emitente." + - name: gnd_codigo + description: "Código do Grupo de Natureza de Despesa." + - name: gnd_nome + description: "Nome do Grupo de Natureza de Despesa." + - name: descricao + description: "Descrição geral do documento." + - name: tipo_nc + description: "Classificação do tipo de Nota de Crédito." + - name: nc_item_detalhamento + description: "Detalhamento dos itens contidos na NC." + - name: ro + description: "Registro de Operação associado." + - name: item_total + description: "Valor total do item." + - name: total_lista + description: "Valor total da lista de itens." + - name: esfera_orcamentaria_codigo + description: "Código da esfera orçamentária." + - name: esfera_orcamentaria_nome + description: "Nome da esfera orçamentária." + + tests: + - verificacao_tipagem: + nome_tabela: 'siafi.nc_tesouro_mir' + nome_coluna: 'ne_ccor_ano_emissao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siafi.nc_tesouro_mir' + nome_coluna: 'despesas_empenhadas' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siafi.nc_tesouro_mir' + nome_coluna: 'despesas_pagas' tipo_esperado: 'numeric' \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/sources.yml b/airflow_lappis/dags/dbt/mir/models/sources.yml index 611e3c56..aae7461f 100644 --- a/airflow_lappis/dags/dbt/mir/models/sources.yml +++ b/airflow_lappis/dags/dbt/mir/models/sources.yml @@ -31,4 +31,6 @@ sources: schema: siafi tables: - name: empenhos_tesouro_parlamentares - - name: empenhos_tesouro_emendas_parlamentares \ No newline at end of file + - name: empenhos_tesouro_emendas_parlamentares + - name: nc_tesouro_pos__2026 + - name: nc_tesouro_pre_2026 \ No newline at end of file From d54730d0a5d655113d69ba9577965e8833825945 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Fri, 10 Apr 2026 14:50:04 -0300 Subject: [PATCH 250/317] chore: renomeia tabelas de teds e dag na dir mir --- .../ne_tesouro_emendas_mir_ingest_dag.py} | 2 +- ...rlamentares_ingest_dag.py => ne_tesouro_mir_ingest_dag.py} | 0 .../dags/dbt/mir/models/emendas_dbt/bronze/tg_emendas.sql | 2 +- .../models/empenhos_ted_dbt/bronze/empenhos_tesouro_ted.sql | 2 +- airflow_lappis/dags/dbt/mir/models/sources.yml | 4 ++-- 5 files changed, 5 insertions(+), 5 deletions(-) rename airflow_lappis/dags/data_ingest/tesouro_gerencial/{empenhos_tesouro_emendas_ingest_dag.py => mir/ne_tesouro_emendas_mir_ingest_dag.py} (99%) rename airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/{empenhos_tesouro_parlamentares_ingest_dag.py => ne_tesouro_mir_ingest_dag.py} (100%) diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_emendas_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/ne_tesouro_emendas_mir_ingest_dag.py similarity index 99% rename from airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_emendas_ingest_dag.py rename to airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/ne_tesouro_emendas_mir_ingest_dag.py index 868b4851..f03e90a2 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/empenhos_tesouro_emendas_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/ne_tesouro_emendas_mir_ingest_dag.py @@ -169,7 +169,7 @@ def insert_data_to_db(**context: Dict[str, Any]) -> None: db.insert_data( data, - "empenhos_tesouro_emendas_parlamentares", + "ne_tesouro_emendas", conflict_fields=unique_key, primary_key=unique_key, schema="siafi", diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/empenhos_tesouro_parlamentares_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/ne_tesouro_mir_ingest_dag.py similarity index 100% rename from airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/empenhos_tesouro_parlamentares_ingest_dag.py rename to airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/ne_tesouro_mir_ingest_dag.py diff --git a/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/tg_emendas.sql b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/tg_emendas.sql index e13e8f4d..9373af6a 100644 --- a/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/tg_emendas.sql +++ b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/tg_emendas.sql @@ -45,7 +45,7 @@ with {{ parse_financial_value("despesas_liquidadas") }} as despesas_liquidadas, {{ parse_financial_value("despesas_pagas") }} as despesas_pagas, (dt_ingest || '-03:00')::timestamptz as dt_ingest - from {{ source("siafi", "empenhos_tesouro_emendas_parlamentares") }} + from {{ source("siafi", "ne_tesouro_emendas") }} ) select * diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/empenhos_tesouro_ted.sql b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/empenhos_tesouro_ted.sql index 72a6916f..f8605312 100644 --- a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/empenhos_tesouro_ted.sql +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/empenhos_tesouro_ted.sql @@ -29,7 +29,7 @@ with {{ parse_financial_value("restos_a_pagar_inscritos") }} as restos_a_pagar_inscritos, {{ parse_financial_value("restos_a_pagar_pagos") }} as restos_a_pagar_pagos, (dt_ingest || '-03:00')::timestamptz as dt_ingest - from {{ source("siafi", "empenhos_tesouro_parlamentares") }} + from {{ source("siafi", "ne_tesouro") }} where ne_ccor_ano_emissao ~ '^[0-9]{4}$' ) diff --git a/airflow_lappis/dags/dbt/mir/models/sources.yml b/airflow_lappis/dags/dbt/mir/models/sources.yml index 611e3c56..0e5b27b3 100644 --- a/airflow_lappis/dags/dbt/mir/models/sources.yml +++ b/airflow_lappis/dags/dbt/mir/models/sources.yml @@ -30,5 +30,5 @@ sources: - name: siafi schema: siafi tables: - - name: empenhos_tesouro_parlamentares - - name: empenhos_tesouro_emendas_parlamentares \ No newline at end of file + - name: ne_tesouro + - name: ne_tesouro_emendas \ No newline at end of file From 265946ac67f30b0f2f23e0e6e672172483109744 Mon Sep 17 00:00:00 2001 From: Mateushqms Date: Fri, 10 Apr 2026 15:52:07 -0300 Subject: [PATCH 251/317] fix: correcao na tipagem devido a uma mudanca na API --- .../dags/dbt/mir/models/emendas_dbt/bronze/executor.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/executor.sql b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/executor.sql index 5ed46bee..000ac71d 100644 --- a/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/executor.sql +++ b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/executor.sql @@ -18,7 +18,7 @@ with nome_agencia_executor::text as nome_agencia_executor, numero_conta_executor::text as numero_conta_executor, dv_conta_executor::text as dv_conta_executor, - codigo_situacao_dado_bancario_executor::integer as codigo_situacao_dado_bancario_executor, + nullif(codigo_situacao_dado_bancario_executor, 'NaN')::numeric::integer as codigo_situacao_dado_bancario_executor, descricao_situacao_dado_bancario_executor::text as descricao_situacao_dado_bancario_executor, (dt_ingest || '-03:00')::timestamptz as dt_ingest from {{ source("transferegov_emendas", "executor_especial") }} From 2e0740b27ba084b46bebf50f9ee75555b5740216 Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira <143732704+davi-aguiar-vieira@users.noreply.github.com> Date: Sun, 12 Apr 2026 13:40:05 -0300 Subject: [PATCH 252/317] =?UTF-8?q?149=20feat=20ingest=C3=A3o=20dos=20dado?= =?UTF-8?q?s=20do=20sisbolsas=20(#193)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(sqlserver): adiciona provider sql server v3.7.2 compatível com airflow 2.8 * feat(sisbolsas): implementa DAG para replicar tabelas do SisBolsas para o Analytics * fix(cliente_sqlserver): correct table name formatting in fetch_table_all method * refactor(dag): remove unnecessary assignment of DAG instance --- .../sisbolsas_to_postgres_ingest_dag.py | 90 +++++++++++++++++++ airflow_lappis/plugins/cliente_sqlserver.py | 56 ++++++++++++ pyproject.toml | 1 + requirements.txt | 3 +- 4 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 airflow_lappis/dags/data_ingest/sisbolsas/sisbolsas_to_postgres_ingest_dag.py create mode 100644 airflow_lappis/plugins/cliente_sqlserver.py diff --git a/airflow_lappis/dags/data_ingest/sisbolsas/sisbolsas_to_postgres_ingest_dag.py b/airflow_lappis/dags/data_ingest/sisbolsas/sisbolsas_to_postgres_ingest_dag.py new file mode 100644 index 00000000..a43ce269 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/sisbolsas/sisbolsas_to_postgres_ingest_dag.py @@ -0,0 +1,90 @@ +import logging +from datetime import datetime, timedelta +from typing import Dict, List, TypedDict +from airflow.decorators import dag, task +from airflow.models import Variable +from cliente_postgres import ClientPostgresDB +from cliente_sqlserver import ClientSQLServerDB +from postgres_helpers import get_postgres_conn +from schedule_loader import get_dynamic_schedule + + +class SQLServerTableConfig(TypedDict): + source_schema: str + source_table: str + target_schema: str + target_table: str + primary_key: List[str] + + +TABLES_TO_SYNC_VARIABLE = "sisbolsas_tables_to_sync" + + +def _load_tables_from_variable() -> List[SQLServerTableConfig]: + return Variable.get( + TABLES_TO_SYNC_VARIABLE, + default_var=[], + deserialize_json=True, + ) + + +@dag( + schedule_interval=get_dynamic_schedule("sisbolsas_to_postgres_ingest_dag"), + start_date=datetime(2024, 1, 1), + catchup=False, + max_active_runs=1, + default_args={ + "owner": "Davi", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["sql_server", "postgres", "sisbolsas"], +) +def sql_server_to_postgres_ingest_dag() -> None: + """Replica tabelas do SisBolsas para o Postgres Analytics.""" + + @task + def replicate_table(table_cfg: SQLServerTableConfig) -> Dict[str, int]: + sql_server = ClientSQLServerDB("mssql_conn_id_sisbolsas") + postgres = ClientPostgresDB(get_postgres_conn()) + source_schema = table_cfg["source_schema"] + source_table = table_cfg["source_table"] + target_schema = table_cfg["target_schema"] + target_table = table_cfg["target_table"] + primary_key = table_cfg["primary_key"] + + source_name = f"{source_schema}.{source_table}" + + logging.info( + "[sql_server_to_postgres_ingest_dag.py] Iniciando replicacao de %s", + source_name, + ) + + rows = sql_server.fetch_table_all( + schema=source_schema, + table_name=source_table, + ) + + if rows: + postgres.insert_data( + data=rows, + table_name=target_table, + conflict_fields=primary_key, + primary_key=primary_key, + schema=target_schema, + ) + + logging.info( + "[sql_server_to_postgres_ingest_dag.py] Replicacao concluida " + "para %s. Registros processados=%s", + source_name, + len(rows), + ) + + return {source_name: len(rows)} + + tables_to_sync = _load_tables_from_variable() + replicate_table.expand(table_cfg=tables_to_sync) + + +sql_server_to_postgres_ingest_dag() diff --git a/airflow_lappis/plugins/cliente_sqlserver.py b/airflow_lappis/plugins/cliente_sqlserver.py new file mode 100644 index 00000000..b779ff01 --- /dev/null +++ b/airflow_lappis/plugins/cliente_sqlserver.py @@ -0,0 +1,56 @@ +import logging +import re +from typing import Any, Dict, List, Tuple +from airflow.providers.microsoft.mssql.hooks.mssql import MsSqlHook + + +class ClientSQLServerDB: + """Client for interacting with SQL Server using Airflow's MsSqlHook.""" + + def __init__(self, mssql_conn_id: str = "mssql_default") -> None: + self.mssql_conn_id = mssql_conn_id + self.hook = MsSqlHook(mssql_conn_id=mssql_conn_id) + logging.info( + "[cliente_sqlserver.py] Initialized ClientSQLServerDB with " + f"mssql_conn_id={mssql_conn_id}" + ) + + def fetch_table_all( + self, + schema: str, + table_name: str, + ) -> List[Dict[str, Any]]: + """Fetch all rows from a SQL Server table using SELECT *.""" + + full_table_name = f"{schema}.{table_name}" + + query = f"SELECT * FROM {full_table_name}" + logging.info(f"[cliente_sqlserver.py] Executing query: {query}") + + conn = self.hook.get_conn() + cursor = conn.cursor() + + try: + cursor.execute(query) + rows = cursor.fetchall() + + if not rows: + logging.info( + "[cliente_sqlserver.py] No rows found in %s", + full_table_name, + ) + return [] + + columns = [description[0] for description in cursor.description] + records = [dict(zip(columns, row)) for row in rows] + + logging.info( + "[cliente_sqlserver.py] Query executed successfully, fetched %s rows " + "from %s", + len(records), + full_table_name, + ) + return records + finally: + cursor.close() + conn.close() diff --git a/pyproject.toml b/pyproject.toml index 74d883aa..06cf5b8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,7 @@ readme = "README.md" python = "~3.11" apache-airflow = "2.8.1" apache-airflow-providers-postgres = "5.14.0" +apache-airflow-providers-microsoft-mssql = "3.7.2" flask-session = "0.5.0" numpy = "1.26.4" flask = "*" diff --git a/requirements.txt b/requirements.txt index 291a0131..8e86e7aa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ sqlparse==0.5 astronomer-cosmos==1.9.0 dbt-postgres==1.7.13 imap-tools==1.10.0 -zeep==4.3.1 \ No newline at end of file +zeep==4.3.1 +apache-airflow-providers-microsoft-mssql==3.7.2 \ No newline at end of file From 80e1d10feb4fd11a0c77efb3219b4323556e6012 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Mon, 13 Apr 2026 13:04:45 -0300 Subject: [PATCH 253/317] feat: bronze pfs do mir implementadas --- .../empenhos_ted_dbt/bronze/pf_ptres.sql | 29 +++++ .../empenhos_ted_dbt/bronze/pf_tesouro.sql | 28 +++++ .../empenhos_ted_dbt/bronze/schema.yaml | 110 ++++++++++++++++++ .../dags/dbt/mir/models/sources.yml | 2 + 4 files changed, 169 insertions(+) create mode 100644 airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/pf_ptres.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/pf_tesouro.sql diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/pf_ptres.sql b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/pf_ptres.sql new file mode 100644 index 00000000..79466999 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/pf_ptres.sql @@ -0,0 +1,29 @@ +{{ config(materialized="table", alias="pf_ptres_mir") }} + + +with + pf_ptres_raw as ( + select + programa_governo::text as programa_governo, + programa_governo_descricao::text as programa_governo_descricao, + plano_orcamentario::text as plano_orcamentario, + plano_orcamentario_descricao_1::text as plano_orcamentario_descricao_1, + plano_orcamentario_descricao_2::text as plano_orcamentario_descricao_2, + plano_orcamentario_descricao_3::text as plano_orcamentario_descricao_3, + plano_orcamentario_descricao_4::text as plano_orcamentario_descricao_4, + plano_orcamentario_descricao_5::text as plano_orcamentario_descricao_5, + plano_orcamentario_descricao_6::text as plano_orcamentario_descricao_6, + acao_governo::text as acao_governo, + acao_governo_descricao::text as acao_governo_descricao, + ptres::text as ptres, + natureza_despesa::text as natureza_despesa, + natureza_despesa_descricao::text as natureza_despesa_descricao, + {{ parse_financial_value("dotacao_inicial") }} as dotacao_inicial, + {{ parse_financial_value("dotacao_suplementar") }} as dotacao_suplementar, + {{ parse_financial_value("dotacao_atualizada") }} as dotacao_atualizada, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("siafi", "programacao_acao_ptres") }} + ) + +select * +from pf_ptres_raw diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/pf_tesouro.sql b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/pf_tesouro.sql new file mode 100644 index 00000000..e906d57d --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/pf_tesouro.sql @@ -0,0 +1,28 @@ +{{ config(materialized="table", alias="pf_tesouro_mir") }} + + +with + pf_tesouro_raw as ( + select + emissao_mes::text as emissao_mes, + to_date(emissao_dia, 'DD/MM/YYYY') as emissao_dia, + ug_emitente::text as ug_emitente, + ug_emitente_descricao::text as ug_emitente_descricao, + ug_favorecido::text as ug_favorecido, + ug_favorecido_descricao::text as ug_favorecido_descricao, + pf_evento::text as pf_evento, + pf_evento_descricao::text as pf_evento_descricao, + pf::text as pf, + pf_inscricao::text as pf_inscricao, + pf_acao::text as pf_acao, + pf_acao_descricao::text as pf_acao_descricao, + pf_fonte_recursos::text as pf_fonte_recursos, + pf_fonte_recursos_descricao::text as pf_fonte_recursos_descricao, + doc_observacao::text as doc_observacao, + {{ parse_financial_value("pf_valor_linha") }} as pf_valor_linha, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("siafi", "pf_tesouro") }} + ) + +select * +from pf_tesouro_raw diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/schema.yaml b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/schema.yaml index 339761d4..4960060c 100644 --- a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/schema.yaml +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/schema.yaml @@ -212,4 +212,114 @@ models: - verificacao_tipagem: nome_tabela: 'siafi.nc_tesouro_mir' nome_coluna: 'despesas_pagas' + tipo_esperado: 'numeric' + + - name: pf_tesouro + description: > + Modelo da camada bronze para programacoes financeiras do Tesouro (PF), + com padronizacao basica de tipos, conversao do valor financeiro para numeric(15,2) + e ajuste de timestamp de ingestao para UTC-3. + meta: + tags: + - bronze + columns: + - name: emissao_mes + description: "Mes de emissao da programacao financeira." + - name: emissao_dia + description: "Data de emissao da programacao financeira." + - name: ug_emitente + description: "Codigo da UG emitente." + - name: ug_emitente_descricao + description: "Descricao da UG emitente." + - name: ug_favorecido + description: "Codigo da UG favorecida." + - name: ug_favorecido_descricao + description: "Descricao da UG favorecida." + - name: pf_evento + description: "Codigo do evento da PF." + - name: pf_evento_descricao + description: "Descricao do evento da PF." + - name: pf + description: "Identificador da programacao financeira." + - name: pf_inscricao + description: "Numero de inscricao associado a PF (ex.: TED)." + - name: pf_acao + description: "Codigo da acao registrada na PF." + - name: pf_acao_descricao + description: "Descricao da acao registrada na PF." + - name: pf_fonte_recursos + description: "Codigo da fonte de recursos da PF." + - name: pf_fonte_recursos_descricao + description: "Descricao da fonte de recursos da PF." + - name: doc_observacao + description: "Observacao textual do documento." + - name: pf_valor_linha + description: "Valor financeiro da linha, convertido para numeric(15,2)." + - name: dt_ingest + description: "Timestamp de ingestao ajustado para UTC-3." + + tests: + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.pf_tesouro_mir' + nome_coluna: 'emissao_dia' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.pf_tesouro_mir' + nome_coluna: 'pf_valor_linha' + tipo_esperado: 'numeric' + + - name: pf_ptres + description: > + Modelo da camada bronze para a programacao de acao por PTRES (SIAFI), + com padronizacao de tipos textuais e conversao dos campos de dotacao + para numeric(15,2), mantendo timestamp de ingestao em UTC-3. + meta: + tags: + - bronze + columns: + - name: programa_governo + description: "Codigo do programa de governo." + - name: programa_governo_descricao + description: "Descricao do programa de governo." + - name: plano_orcamentario + description: "Codigo do plano orcamentario." + - name: plano_orcamentario_descricao_1 + description: "Descricao 1 do plano orcamentario." + - name: plano_orcamentario_descricao_2 + description: "Descricao 2 do plano orcamentario." + - name: plano_orcamentario_descricao_3 + description: "Descricao 3 do plano orcamentario." + - name: plano_orcamentario_descricao_4 + description: "Descricao 4 do plano orcamentario." + - name: plano_orcamentario_descricao_5 + description: "Descricao 5 do plano orcamentario." + - name: plano_orcamentario_descricao_6 + description: "Descricao 6 do plano orcamentario." + - name: acao_governo + description: "Codigo da acao de governo." + - name: acao_governo_descricao + description: "Descricao da acao de governo." + - name: ptres + description: "Codigo PTRES associado a programacao." + - name: natureza_despesa + description: "Codigo da natureza de despesa." + - name: natureza_despesa_descricao + description: "Descricao da natureza de despesa." + - name: dotacao_inicial + description: "Valor da dotacao inicial convertido para numeric(15,2)." + - name: dotacao_suplementar + description: "Valor da dotacao suplementar convertido para numeric(15,2)." + - name: dotacao_atualizada + description: "Valor da dotacao atualizada convertido para numeric(15,2)." + - name: dt_ingest + description: "Timestamp de ingestao ajustado para UTC-3." + + tests: + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.pf_ptres_mir' + nome_coluna: 'dotacao_inicial' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.pf_ptres_mir' + nome_coluna: 'dotacao_atualizada' tipo_esperado: 'numeric' \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/sources.yml b/airflow_lappis/dags/dbt/mir/models/sources.yml index e552056d..82caeb8b 100644 --- a/airflow_lappis/dags/dbt/mir/models/sources.yml +++ b/airflow_lappis/dags/dbt/mir/models/sources.yml @@ -34,3 +34,5 @@ sources: - name: ne_tesouro_emendas - name: nc_tesouro_pos__2026 - name: nc_tesouro_pre_2026 + - name: pf_tesouro + - name: programacao_acao_ptres From 47b3aa5fdfc2b8b08eda10496633788f85aa3029 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Mon, 13 Apr 2026 15:48:47 -0300 Subject: [PATCH 254/317] feat: bronze pfs mir transferegov --- .../empenhos_ted_dbt/bronze/pf_transfere.sql | 22 ++++++++++ .../empenhos_ted_dbt/bronze/schema.yaml | 44 ++++++++++++++++++- .../dags/dbt/mir/models/sources.yml | 5 +++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/pf_transfere.sql diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/pf_transfere.sql b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/pf_transfere.sql new file mode 100644 index 00000000..17fd5502 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/pf_transfere.sql @@ -0,0 +1,22 @@ +{{ config(materialized="table", alias="pf_transfere_mir") }} + + +with + pf_transfere_raw as ( + select + id_programacao::integer as id_programacao, + id_plano_acao::integer as id_plano_acao, + tp_pf_tipo_programacao::text as tp_pf_tipo_programacao, + tx_minuta_programacao::text as tx_minuta_programacao, + tx_numero_programacao::text as tx_numero_programacao, + tx_situacao_programacao::text as tx_situacao_programacao, + tx_observacao_programacao::text as tx_observacao_programacao, + ug_emitente_programacao::text as ug_emitente_programacao, + ug_favorecida_programacao::text as ug_favorecida_programacao, + dh_recebimento_programacao::timestamp as dh_recebimento_programacao, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("transfere_gov", "programacao_financeira") }} + ) + +select * +from pf_transfere_raw diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/schema.yaml b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/schema.yaml index 4960060c..2af4580a 100644 --- a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/schema.yaml +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/schema.yaml @@ -322,4 +322,46 @@ models: - verificacao_tipagem: nome_tabela: 'siafi_dbt.pf_ptres_mir' nome_coluna: 'dotacao_atualizada' - tipo_esperado: 'numeric' \ No newline at end of file + tipo_esperado: 'numeric' + + - name: pf_transfere + description: > + Modelo da camada bronze para programacao financeira do Transferegov, + com padronizacao de tipos de identificadores e campos textuais, + alem do ajuste de dt_ingest para timestamp com fuso UTC-3. + meta: + tags: + - bronze + columns: + - name: id_programacao + description: "Identificador da programacao financeira." + - name: id_plano_acao + description: "Identificador do plano de acao no Transferegov." + - name: tp_pf_tipo_programacao + description: "Tipo da programacao financeira (ex.: T)." + - name: tx_minuta_programacao + description: "Identificador da minuta/programacao (formato MPF)." + - name: tx_numero_programacao + description: "Numero da programacao financeira." + - name: tx_situacao_programacao + description: "Situacao da programacao financeira." + - name: tx_observacao_programacao + description: "Descricao textual/observacao da programacao." + - name: ug_emitente_programacao + description: "Codigo da UG emitente da programacao." + - name: ug_favorecida_programacao + description: "Codigo da UG favorecida da programacao." + - name: dh_recebimento_programacao + description: "Timestamp de recebimento/cadastro da programacao no sistema de origem." + - name: dt_ingest + description: "Timestamp de ingestao ajustado para UTC-3." + + tests: + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.pf_transfere_mir' + nome_coluna: 'id_plano_acao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.pf_transfere_mir' + nome_coluna: 'id_programacao' + tipo_esperado: 'integer' \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/sources.yml b/airflow_lappis/dags/dbt/mir/models/sources.yml index 82caeb8b..8c9f11eb 100644 --- a/airflow_lappis/dags/dbt/mir/models/sources.yml +++ b/airflow_lappis/dags/dbt/mir/models/sources.yml @@ -27,6 +27,11 @@ sources: tables: - name: senadores + - name: transfere_gov + schema: transfere_gov + tables: + - name: programacao_financeira + - name: siafi schema: siafi tables: From 7bf85b9c5f499005cab9b06c7382db3070459ad6 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Mon, 13 Apr 2026 16:05:33 -0300 Subject: [PATCH 255/317] feat: silver pfs unificadas tesouro trasnferegov --- .../empenhos_ted_dbt/silver/pf_unificado.sql | 73 ++++++++++++++++ .../models/empenhos_ted_dbt/silver/schema.yml | 86 +++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/pf_unificado.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/schema.yml diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/pf_unificado.sql b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/pf_unificado.sql new file mode 100644 index 00000000..e13e6e14 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/pf_unificado.sql @@ -0,0 +1,73 @@ +{{ config(materialized="table") }} + +with + pf_tesouro as ( + select + emissao_mes, + emissao_dia, + ug_emitente, + ug_emitente_descricao, + ug_favorecido, + ug_favorecido_descricao, + pf_evento, + pf_evento_descricao, + pf, + pf_inscricao, + pf_acao, + pf_acao_descricao, + pf_fonte_recursos, + pf_fonte_recursos_descricao, + doc_observacao, + pf_valor_linha, + dt_ingest as dt_ingest_tesouro, + upper(trim(right(pf, 12))) as pf_chave + from {{ ref("pf_tesouro") }} + ), + pf_transfere as ( + select + id_programacao, + id_plano_acao, + tp_pf_tipo_programacao, + tx_minuta_programacao, + tx_numero_programacao, + tx_situacao_programacao, + tx_observacao_programacao, + ug_emitente_programacao, + ug_favorecida_programacao, + dh_recebimento_programacao, + dt_ingest as dt_ingest_transfere, + upper(trim(tx_numero_programacao)) as pf_chave + from {{ ref("pf_transfere") }} + ) + +select + t.emissao_mes, + t.emissao_dia, + t.ug_emitente, + t.ug_emitente_descricao, + t.ug_favorecido, + t.ug_favorecido_descricao, + t.pf_evento, + t.pf_evento_descricao, + t.pf, + t.pf_inscricao, + t.pf_acao, + t.pf_acao_descricao, + t.pf_fonte_recursos, + t.pf_fonte_recursos_descricao, + t.doc_observacao, + t.pf_valor_linha, + p.id_programacao, + p.id_plano_acao, + p.tp_pf_tipo_programacao, + p.tx_minuta_programacao, + p.tx_numero_programacao, + p.tx_situacao_programacao, + p.tx_observacao_programacao, + p.ug_emitente_programacao, + p.ug_favorecida_programacao, + p.dh_recebimento_programacao, + t.pf_chave, + greatest(t.dt_ingest_tesouro, p.dt_ingest_transfere) as dt_ingest +from pf_tesouro t +inner join pf_transfere p using (pf_chave) diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/schema.yml b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/schema.yml new file mode 100644 index 00000000..2b1d42f8 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/schema.yml @@ -0,0 +1,86 @@ +version: 2 + +models: + - name: pf_unificado + description: > + Tabela silver que unifica as programacoes financeiras do Tesouro Gerencial + (pf_tesouro) e do Transferegov (pf_transfere), realizando o cruzamento pela + chave normalizada da PF. A chave e formada pelos ultimos 12 caracteres do campo + pf no Tesouro e comparada com tx_numero_programacao no Transferegov. + meta: + tags: + - silver + columns: + - name: emissao_mes + description: "Mes de emissao da programacao financeira no Tesouro." + - name: emissao_dia + description: "Data de emissao da programacao financeira no Tesouro." + - name: ug_emitente + description: "Codigo da UG emitente no Tesouro Gerencial." + - name: ug_emitente_descricao + description: "Descricao da UG emitente no Tesouro Gerencial." + - name: ug_favorecido + description: "Codigo da UG favorecida no Tesouro Gerencial." + - name: ug_favorecido_descricao + description: "Descricao da UG favorecida no Tesouro Gerencial." + - name: pf_evento + description: "Codigo do evento da programacao financeira no Tesouro." + - name: pf_evento_descricao + description: "Descricao do evento da programacao financeira no Tesouro." + - name: pf + description: "Identificador completo da PF no Tesouro Gerencial." + - name: pf_inscricao + description: "Inscricao/numero associado a PF no Tesouro." + - name: pf_acao + description: "Codigo da acao vinculada a PF no Tesouro." + - name: pf_acao_descricao + description: "Descricao da acao vinculada a PF no Tesouro." + - name: pf_fonte_recursos + description: "Codigo da fonte de recursos da PF no Tesouro." + - name: pf_fonte_recursos_descricao + description: "Descricao da fonte de recursos da PF no Tesouro." + - name: doc_observacao + description: "Observacao textual do documento no Tesouro." + - name: pf_valor_linha + description: "Valor financeiro da linha da PF no Tesouro." + - name: id_programacao + description: "Identificador da programacao financeira no Transferegov." + - name: id_plano_acao + description: "Identificador do plano de acao no Transferegov." + - name: tp_pf_tipo_programacao + description: "Tipo da programacao financeira no Transferegov." + - name: tx_minuta_programacao + description: "Numero/texto da minuta de programacao no Transferegov." + - name: tx_numero_programacao + description: "Numero da programacao financeira no Transferegov." + - name: tx_situacao_programacao + description: "Situacao da programacao financeira no Transferegov." + - name: tx_observacao_programacao + description: "Observacao/objeto da programacao no Transferegov." + - name: ug_emitente_programacao + description: "Codigo da UG emitente da programacao no Transferegov." + - name: ug_favorecida_programacao + description: "Codigo da UG favorecida da programacao no Transferegov." + - name: dh_recebimento_programacao + description: "Timestamp de recebimento/cadastro da programacao no Transferegov." + - name: pf_chave + description: "Chave normalizada usada no join entre Tesouro e Transferegov." + - name: dt_ingest + description: "Maior timestamp de ingestao entre as fontes (UTC-3)." + tests: + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.pf_unificado' + nome_coluna: 'emissao_dia' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.pf_unificado' + nome_coluna: 'pf_valor_linha' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.pf_unificado' + nome_coluna: 'id_programacao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.pf_unificado' + nome_coluna: 'id_plano_acao' + tipo_esperado: 'integer' From 3e8ff0a894a723dbc2033bb2c44faa286b5317df Mon Sep 17 00:00:00 2001 From: Luana Date: Sat, 11 Apr 2026 10:24:01 -0300 Subject: [PATCH 256/317] =?UTF-8?q?feat:=20adiciona=20dag=20de=20ingest?= =?UTF-8?q?=C3=A3o=20do=20siconv=20com=20tabelas=20proposta=20e=20convenio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dags/data_ingest/siconv/cliente_siconv.py | 35 ++++++++++ .../data_ingest/siconv/sincov_ingest_dag.py | 67 +++++++++++++++++++ .../dags/data_ingest/siconv/tabelas_siconv.py | 16 +++++ 3 files changed, 118 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/siconv/cliente_siconv.py create mode 100644 airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py create mode 100644 airflow_lappis/dags/data_ingest/siconv/tabelas_siconv.py diff --git a/airflow_lappis/dags/data_ingest/siconv/cliente_siconv.py b/airflow_lappis/dags/data_ingest/siconv/cliente_siconv.py new file mode 100644 index 00000000..c6cbdcd8 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/siconv/cliente_siconv.py @@ -0,0 +1,35 @@ +import zipfile +import csv +import io +import logging +import requests + + +class ClienteSiconv: + URL_ZIP = "https://repositorio.dados.gov.br/seges/detru/siconv.zip" + ZIP_PATH = "/tmp/siconv.zip" + + def baixar_zip(self) -> None: + logging.info("[cliente_siconv.py] Baixando arquivo SICONV...") + response = requests.get(self.URL_ZIP, stream=True) + with open(self.ZIP_PATH, "wb") as f: + for chunk in response.iter_content(chunk_size=8192): + f.write(chunk) + logging.info("[cliente_siconv.py] Download concluído") + + def ler_csv(self, nome_csv: str, skip_rows: int = 0) -> list[dict]: + logging.info(f"[cliente_siconv.py] Lendo {nome_csv}...") + registros = [] + + with zipfile.ZipFile(self.ZIP_PATH, "r") as z: + with z.open(nome_csv) as f: + conteudo = io.TextIOWrapper(f, encoding="utf-8") + reader = csv.DictReader(conteudo, delimiter=";") + + for i, row in enumerate(reader): + if i < skip_rows: + continue + registros.append({k.lower(): v for k, v in row.items()}) + + logging.info(f"[cliente_siconv.py] {len(registros)} registros lidos de {nome_csv}") + return registros \ No newline at end of file diff --git a/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py b/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py new file mode 100644 index 00000000..897b8c1c --- /dev/null +++ b/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py @@ -0,0 +1,67 @@ +import os +import logging +import requests +import zipfile +from datetime import datetime, timedelta +from airflow.decorators import dag, task +from postgres_helpers import get_postgres_conn +from cliente_postgres import ClientPostgresDB +from cliente_siconv import ClienteSiconv +from tabelas_siconv import TABELAS_SICONV + + +@dag( + schedule_interval="@daily", + start_date=datetime(2024, 1, 1), + catchup=False, + default_args={ + "owner": "Luana", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["siconv", "data_ingest"], +) +def siconv_ingestao_dag() -> None: + + @task + def baixar_siconv() -> str: + cliente = ClienteSiconv() + cliente.baixar_zip() + return cliente.ZIP_PATH + + @task + def ingerir_tabela(zip_path: str, nome_tabela: str, nome_csv: str, conflict_fields: list, primary_key: list, skip_rows: int) -> None: + logging.info(f"[siconv_ingest_dag.py] Iniciando ingestão da tabela {nome_tabela}") + postgres_conn_str = get_postgres_conn("postgres_mir") + db = ClientPostgresDB(postgres_conn_str) + + cliente = ClienteSiconv() + registros = cliente.ler_csv(nome_csv, skip_rows) + + if not registros: + logging.warning(f"[siconv_ingest_dag.py] Nenhum registro encontrado em {nome_csv}") + return + + db.insert_data( + registros, + nome_tabela, + conflict_fields=conflict_fields, + primary_key=primary_key, + schema="siconv", + ) + + logging.info(f"[siconv_ingest_dag.py] {len(registros)} registros inseridos em {nome_tabela}") + + zip_path = baixar_siconv() + for tabela in TABELAS_SICONV: + ingerir_tabela( + zip_path=zip_path, + nome_tabela=tabela["nome_tabela"], + nome_csv=tabela["nome_csv"], + conflict_fields=tabela["conflict_fields"], + primary_key=tabela["primary_key"], + skip_rows=tabela["skip_rows"], + ) + + +siconv_ingestao_dag() \ No newline at end of file diff --git a/airflow_lappis/dags/data_ingest/siconv/tabelas_siconv.py b/airflow_lappis/dags/data_ingest/siconv/tabelas_siconv.py new file mode 100644 index 00000000..c8424d9f --- /dev/null +++ b/airflow_lappis/dags/data_ingest/siconv/tabelas_siconv.py @@ -0,0 +1,16 @@ +TABELAS_SICONV = [ + { + "nome_tabela": "proposta", + "nome_csv": "siconv_proposta.csv", + "conflict_fields": ["id_proposta"], + "primary_key": ["id_proposta"], + "skip_rows": 1, + }, + { + "nome_tabela": "convenio", + "nome_csv": "siconv_convenio.csv", + "conflict_fields": ["nr_convenio"], + "primary_key": ["nr_convenio"], + "skip_rows": 0, + }, +] \ No newline at end of file From 0f1026af3fd6beafedbd7795f15f02254d4adcbf Mon Sep 17 00:00:00 2001 From: Luana Date: Sat, 11 Apr 2026 10:32:28 -0300 Subject: [PATCH 257/317] =?UTF-8?q?feat:=20adiciona=20dag=20de=20ingest?= =?UTF-8?q?=C3=A3o=20do=20siconv=20com=20tabelas=20proposta=20e=20convenio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{dags/data_ingest/siconv => plugins}/cliente_siconv.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename airflow_lappis/{dags/data_ingest/siconv => plugins}/cliente_siconv.py (100%) diff --git a/airflow_lappis/dags/data_ingest/siconv/cliente_siconv.py b/airflow_lappis/plugins/cliente_siconv.py similarity index 100% rename from airflow_lappis/dags/data_ingest/siconv/cliente_siconv.py rename to airflow_lappis/plugins/cliente_siconv.py From b84b4c587001426bdcbb3730e203b13a6d7d127c Mon Sep 17 00:00:00 2001 From: Luana Date: Sat, 11 Apr 2026 10:35:30 -0300 Subject: [PATCH 258/317] =?UTF-8?q?feat:=20adiciona=20dag=20de=20ingest?= =?UTF-8?q?=C3=A3o=20do=20siconv=20com=20tabelas=20proposta=20e=20convenio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{dags/data_ingest/siconv => plugins}/tabelas_siconv.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename airflow_lappis/{dags/data_ingest/siconv => plugins}/tabelas_siconv.py (100%) diff --git a/airflow_lappis/dags/data_ingest/siconv/tabelas_siconv.py b/airflow_lappis/plugins/tabelas_siconv.py similarity index 100% rename from airflow_lappis/dags/data_ingest/siconv/tabelas_siconv.py rename to airflow_lappis/plugins/tabelas_siconv.py From 9604bc4d64cc01e124d821cbe1bdd6c5fa317f4b Mon Sep 17 00:00:00 2001 From: Luana Date: Sat, 11 Apr 2026 12:30:44 -0300 Subject: [PATCH 259/317] feat: ajusta colunas das tabelas proposta e convenio --- .../data_ingest/siconv/sincov_ingest_dag.py | 8 +++---- airflow_lappis/plugins/cliente_siconv.py | 9 ++++++- airflow_lappis/plugins/tabelas_siconv.py | 24 ++++++++++++++++++- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py b/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py index 897b8c1c..56d8a700 100644 --- a/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py @@ -1,7 +1,4 @@ -import os import logging -import requests -import zipfile from datetime import datetime, timedelta from airflow.decorators import dag, task from postgres_helpers import get_postgres_conn @@ -30,13 +27,13 @@ def baixar_siconv() -> str: return cliente.ZIP_PATH @task - def ingerir_tabela(zip_path: str, nome_tabela: str, nome_csv: str, conflict_fields: list, primary_key: list, skip_rows: int) -> None: + def ingerir_tabela(zip_path: str, nome_tabela: str, nome_csv: str, conflict_fields: list, primary_key: list, skip_rows: int, colunas: list) -> None: logging.info(f"[siconv_ingest_dag.py] Iniciando ingestão da tabela {nome_tabela}") postgres_conn_str = get_postgres_conn("postgres_mir") db = ClientPostgresDB(postgres_conn_str) cliente = ClienteSiconv() - registros = cliente.ler_csv(nome_csv, skip_rows) + registros = cliente.ler_csv(nome_csv, skip_rows, colunas_esperadas=colunas) if not registros: logging.warning(f"[siconv_ingest_dag.py] Nenhum registro encontrado em {nome_csv}") @@ -61,6 +58,7 @@ def ingerir_tabela(zip_path: str, nome_tabela: str, nome_csv: str, conflict_fiel conflict_fields=tabela["conflict_fields"], primary_key=tabela["primary_key"], skip_rows=tabela["skip_rows"], + colunas=tabela["colunas"], ) diff --git a/airflow_lappis/plugins/cliente_siconv.py b/airflow_lappis/plugins/cliente_siconv.py index c6cbdcd8..9a0d0552 100644 --- a/airflow_lappis/plugins/cliente_siconv.py +++ b/airflow_lappis/plugins/cliente_siconv.py @@ -17,7 +17,7 @@ def baixar_zip(self) -> None: f.write(chunk) logging.info("[cliente_siconv.py] Download concluído") - def ler_csv(self, nome_csv: str, skip_rows: int = 0) -> list[dict]: + def ler_csv(self, nome_csv: str, skip_rows: int = 0, colunas_esperadas: list = None) -> list[dict]: logging.info(f"[cliente_siconv.py] Lendo {nome_csv}...") registros = [] @@ -26,6 +26,13 @@ def ler_csv(self, nome_csv: str, skip_rows: int = 0) -> list[dict]: conteudo = io.TextIOWrapper(f, encoding="utf-8") reader = csv.DictReader(conteudo, delimiter=";") + if colunas_esperadas: + colunas_csv = reader.fieldnames + faltando = [c for c in colunas_esperadas if c not in colunas_csv] + if faltando: + raise ValueError(f"[cliente_siconv.py] Colunas faltando em {nome_csv}: {faltando}") + logging.info(f"[cliente_siconv.py] Validação de colunas OK para {nome_csv}") + for i, row in enumerate(reader): if i < skip_rows: continue diff --git a/airflow_lappis/plugins/tabelas_siconv.py b/airflow_lappis/plugins/tabelas_siconv.py index c8424d9f..f91c001c 100644 --- a/airflow_lappis/plugins/tabelas_siconv.py +++ b/airflow_lappis/plugins/tabelas_siconv.py @@ -4,7 +4,16 @@ "nome_csv": "siconv_proposta.csv", "conflict_fields": ["id_proposta"], "primary_key": ["id_proposta"], - "skip_rows": 1, + "skip_rows": 0, + "colunas": [ + "ID_PROPOSTA", "UF_PROPONENTE", "MUNIC_PROPONENTE", "COD_MUNIC_IBGE", + "COD_ORGAO_SUP", "DESC_ORGAO_SUP", "NATUREZA_JURIDICA", "NR_PROPOSTA", + "DIA_PROP", "MES_PROP", "ANO_PROP", "DIA_PROPOSTA", "COD_ORGAO", + "DESC_ORGAO", "MODALIDADE", "IDENTIF_PROPONENTE", "NM_PROPONENTE", + "CEP_PROPONENTE", "ENDERECO_PROPONENTE", "BAIRRO_PROPONENTE", "NM_BANCO", + "NOME_SUBTIPO_PROPOSTA", "DESCRICAO_SUBTIPO_PROPOSTA", "VL_GLOBAL_PROP", + "VL_REPASSE_PROP", "VL_CONTRAPARTIDA_PROP", "CD_AGENCIA", "CD_CONTA", + ], }, { "nome_tabela": "convenio", @@ -12,5 +21,18 @@ "conflict_fields": ["nr_convenio"], "primary_key": ["nr_convenio"], "skip_rows": 0, + "colunas": [ + "NR_CONVENIO", "ID_PROPOSTA", "DIA", "MES", "ANO", "DIA_ASSIN_CONV", + "SIT_CONVENIO", "SUBSITUACAO_CONV", "SITUACAO_PUBLICACAO", "INSTRUMENTO_ATIVO", + "IND_OPERA_OBTV", "NR_PROCESSO", "UG_EMITENTE", "DIA_PUBL_CONV", + "DIA_INIC_VIGENC_CONV", "DIA_FIM_VIGENC_CONV", "DIA_FIM_VIGENC_ORIGINAL_CONV", + "DIAS_PREST_CONTAS", "DIA_LIMITE_PREST_CONTAS", "DATA_SUSPENSIVA", + "DATA_RETIRADA_SUSPENSIVA", "DIAS_CLAUSULA_SUSPENSIVA", "SITUACAO_CONTRATACAO", + "IND_ASSINADO", "MOTIVO_SUSPENSAO", "IND_FOTO", "QTDE_CONVENIOS", "QTD_TA", + "QTD_PRORROGA", "VL_GLOBAL_CONV", "VL_REPASSE_CONV", "VL_CONTRAPARTIDA_CONV", + "VL_EMPENHADO_CONV", "VL_DESEMBOLSADO_CONV", "VL_SALDO_REMAN_TESOURO", + "VL_SALDO_REMAN_CONVENENTE", "VL_RENDIMENTO_APLICACAO", "VL_INGRESSO_CONTRAPARTIDA", + "VL_SALDO_CONTA", "VALOR_GLOBAL_ORIGINAL_CONV", + ], }, ] \ No newline at end of file From a0b2647af435ad7d85168965c6fd1ce679ece37a Mon Sep 17 00:00:00 2001 From: Luana Date: Sat, 11 Apr 2026 15:51:12 -0300 Subject: [PATCH 260/317] =?UTF-8?q?fix:=20corre=C3=A7=C3=A3o=20de=20estour?= =?UTF-8?q?o=20de=20mem=C3=B3ria=20(OOM)=20na=20ingest=C3=A3o=20de=20tabel?= =?UTF-8?q?as=20grandes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data_ingest/siconv/sincov_ingest_dag.py | 52 +++++++++++++------ airflow_lappis/plugins/cliente_siconv.py | 20 +++---- 2 files changed, 42 insertions(+), 30 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py b/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py index 56d8a700..4d967154 100644 --- a/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py @@ -6,7 +6,6 @@ from cliente_siconv import ClienteSiconv from tabelas_siconv import TABELAS_SICONV - @dag( schedule_interval="@daily", start_date=datetime(2024, 1, 1), @@ -31,28 +30,48 @@ def ingerir_tabela(zip_path: str, nome_tabela: str, nome_csv: str, conflict_fiel logging.info(f"[siconv_ingest_dag.py] Iniciando ingestão da tabela {nome_tabela}") postgres_conn_str = get_postgres_conn("postgres_mir") db = ClientPostgresDB(postgres_conn_str) - cliente = ClienteSiconv() - registros = cliente.ler_csv(nome_csv, skip_rows, colunas_esperadas=colunas) + + gerador_registros = cliente.ler_csv(nome_csv, skip_rows, colunas_esperadas=colunas) - if not registros: - logging.warning(f"[siconv_ingest_dag.py] Nenhum registro encontrado em {nome_csv}") - return + lote = [] + tamanho_lote = 5000 + total_inserido = 0 - db.insert_data( - registros, - nome_tabela, - conflict_fields=conflict_fields, - primary_key=primary_key, - schema="siconv", - ) + for registro in gerador_registros: + lote.append(registro) + + if len(lote) >= tamanho_lote: + db.insert_data( + lote, + nome_tabela, + conflict_fields=conflict_fields, + primary_key=primary_key, + schema="siconv", + ) + total_inserido += len(lote) + logging.info(f"[siconv_ingest_dag.py] {total_inserido} registros processados...") + lote = [] - logging.info(f"[siconv_ingest_dag.py] {len(registros)} registros inseridos em {nome_tabela}") + if lote: + db.insert_data( + lote, + nome_tabela, + conflict_fields=conflict_fields, + primary_key=primary_key, + schema="siconv", + ) + total_inserido += len(lote) - zip_path = baixar_siconv() + if total_inserido == 0: + logging.warning(f"[siconv_ingest_dag.py] Nenhum registro processado para {nome_tabela}") + else: + logging.info(f"[siconv_ingest_dag.py] Ingestão finalizada: {total_inserido} registros em {nome_tabela}") + + path_zip = baixar_siconv() for tabela in TABELAS_SICONV: ingerir_tabela( - zip_path=zip_path, + zip_path=path_zip, nome_tabela=tabela["nome_tabela"], nome_csv=tabela["nome_csv"], conflict_fields=tabela["conflict_fields"], @@ -61,5 +80,4 @@ def ingerir_tabela(zip_path: str, nome_tabela: str, nome_csv: str, conflict_fiel colunas=tabela["colunas"], ) - siconv_ingestao_dag() \ No newline at end of file diff --git a/airflow_lappis/plugins/cliente_siconv.py b/airflow_lappis/plugins/cliente_siconv.py index 9a0d0552..5f5e050b 100644 --- a/airflow_lappis/plugins/cliente_siconv.py +++ b/airflow_lappis/plugins/cliente_siconv.py @@ -4,39 +4,33 @@ import logging import requests - class ClienteSiconv: URL_ZIP = "https://repositorio.dados.gov.br/seges/detru/siconv.zip" - ZIP_PATH = "/tmp/siconv.zip" + ZIP_PATH = "/opt/airflow/dags/data_ingest/siconv/siconv.zip" def baixar_zip(self) -> None: logging.info("[cliente_siconv.py] Baixando arquivo SICONV...") response = requests.get(self.URL_ZIP, stream=True) + response.raise_for_status() with open(self.ZIP_PATH, "wb") as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) logging.info("[cliente_siconv.py] Download concluído") - def ler_csv(self, nome_csv: str, skip_rows: int = 0, colunas_esperadas: list = None) -> list[dict]: - logging.info(f"[cliente_siconv.py] Lendo {nome_csv}...") - registros = [] - + def ler_csv(self, nome_csv: str, skip_rows: int = 0, colunas_esperadas: list = None): + logging.info(f"[cliente_siconv.py] Lendo {nome_csv} em modo streaming...") with zipfile.ZipFile(self.ZIP_PATH, "r") as z: with z.open(nome_csv) as f: - conteudo = io.TextIOWrapper(f, encoding="utf-8") + conteudo = io.TextIOWrapper(f, encoding="utf-8-sig") reader = csv.DictReader(conteudo, delimiter=";") - + if colunas_esperadas: colunas_csv = reader.fieldnames faltando = [c for c in colunas_esperadas if c not in colunas_csv] if faltando: raise ValueError(f"[cliente_siconv.py] Colunas faltando em {nome_csv}: {faltando}") - logging.info(f"[cliente_siconv.py] Validação de colunas OK para {nome_csv}") for i, row in enumerate(reader): if i < skip_rows: continue - registros.append({k.lower(): v for k, v in row.items()}) - - logging.info(f"[cliente_siconv.py] {len(registros)} registros lidos de {nome_csv}") - return registros \ No newline at end of file + yield {k.lower(): v for k, v in row.items()} \ No newline at end of file From 1804b74893cb36761bcc92ec9286f06c7a822c77 Mon Sep 17 00:00:00 2001 From: Luana Date: Mon, 13 Apr 2026 21:48:54 -0300 Subject: [PATCH 261/317] =?UTF-8?q?feat:=20adiciona=20dag=20de=20ingest?= =?UTF-8?q?=C3=A3o=20de=20dados=20do=20SICONV=20com=20estrat=C3=A9gias=20d?= =?UTF-8?q?e=20upsert=20e=20truncate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data_ingest/siconv/sincov_ingest_dag.py | 24 ++- airflow_lappis/plugins/cliente_siconv.py | 8 +- airflow_lappis/plugins/tabelas_siconv.py | 186 ++++++++++++++++++ 3 files changed, 213 insertions(+), 5 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py b/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py index 4d967154..0aa2692b 100644 --- a/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py @@ -26,12 +26,21 @@ def baixar_siconv() -> str: return cliente.ZIP_PATH @task - def ingerir_tabela(zip_path: str, nome_tabela: str, nome_csv: str, conflict_fields: list, primary_key: list, skip_rows: int, colunas: list) -> None: + def ingerir_tabela(zip_path: str, nome_tabela: str, nome_csv: str, conflict_fields: list, primary_key: list, skip_rows: int, colunas: list, truncate_before_insert: bool = False) -> None: logging.info(f"[siconv_ingest_dag.py] Iniciando ingestão da tabela {nome_tabela}") postgres_conn_str = get_postgres_conn("postgres_mir") db = ClientPostgresDB(postgres_conn_str) cliente = ClienteSiconv() - + + if truncate_before_insert: + logging.info(f"[siconv_ingest_dag.py] Truncando tabela siconv.{nome_tabela}...") + db.execute_non_query(f""" + DO $$ BEGIN + IF EXISTS (SELECT FROM pg_tables WHERE schemaname = 'siconv' AND tablename = '{nome_tabela}') THEN + TRUNCATE TABLE siconv.{nome_tabela}; + END IF; + END $$; + """) gerador_registros = cliente.ler_csv(nome_csv, skip_rows, colunas_esperadas=colunas) lote = [] @@ -42,6 +51,7 @@ def ingerir_tabela(zip_path: str, nome_tabela: str, nome_csv: str, conflict_fiel lote.append(registro) if len(lote) >= tamanho_lote: + lote = [dict(t) for t in {tuple(d.items()) for d in lote}] db.insert_data( lote, nome_tabela, @@ -54,6 +64,7 @@ def ingerir_tabela(zip_path: str, nome_tabela: str, nome_csv: str, conflict_fiel lote = [] if lote: + lote = [dict(t) for t in {tuple(d.items()) for d in lote}] db.insert_data( lote, nome_tabela, @@ -69,8 +80,11 @@ def ingerir_tabela(zip_path: str, nome_tabela: str, nome_csv: str, conflict_fiel logging.info(f"[siconv_ingest_dag.py] Ingestão finalizada: {total_inserido} registros em {nome_tabela}") path_zip = baixar_siconv() + + ultima_task = path_zip + for tabela in TABELAS_SICONV: - ingerir_tabela( + task_atual = ingerir_tabela.override(task_id=f"ingerir_{tabela['nome_tabela']}")( zip_path=path_zip, nome_tabela=tabela["nome_tabela"], nome_csv=tabela["nome_csv"], @@ -78,6 +92,10 @@ def ingerir_tabela(zip_path: str, nome_tabela: str, nome_csv: str, conflict_fiel primary_key=tabela["primary_key"], skip_rows=tabela["skip_rows"], colunas=tabela["colunas"], + truncate_before_insert=tabela.get("truncate_before_insert", False), ) + ultima_task >> task_atual + ultima_task = task_atual + siconv_ingestao_dag() \ No newline at end of file diff --git a/airflow_lappis/plugins/cliente_siconv.py b/airflow_lappis/plugins/cliente_siconv.py index 5f5e050b..0921b28f 100644 --- a/airflow_lappis/plugins/cliente_siconv.py +++ b/airflow_lappis/plugins/cliente_siconv.py @@ -25,7 +25,7 @@ def ler_csv(self, nome_csv: str, skip_rows: int = 0, colunas_esperadas: list = N reader = csv.DictReader(conteudo, delimiter=";") if colunas_esperadas: - colunas_csv = reader.fieldnames + colunas_csv = reader.fieldnames or [] faltando = [c for c in colunas_esperadas if c not in colunas_csv] if faltando: raise ValueError(f"[cliente_siconv.py] Colunas faltando em {nome_csv}: {faltando}") @@ -33,4 +33,8 @@ def ler_csv(self, nome_csv: str, skip_rows: int = 0, colunas_esperadas: list = N for i, row in enumerate(reader): if i < skip_rows: continue - yield {k.lower(): v for k, v in row.items()} \ No newline at end of file + + if colunas_esperadas: + yield {k.lower(): row[k] for k in colunas_esperadas} + else: + yield {k.lower(): v for k, v in row.items() if k is not None} \ No newline at end of file diff --git a/airflow_lappis/plugins/tabelas_siconv.py b/airflow_lappis/plugins/tabelas_siconv.py index f91c001c..985afa5a 100644 --- a/airflow_lappis/plugins/tabelas_siconv.py +++ b/airflow_lappis/plugins/tabelas_siconv.py @@ -4,6 +4,7 @@ "nome_csv": "siconv_proposta.csv", "conflict_fields": ["id_proposta"], "primary_key": ["id_proposta"], + "truncate_before_insert": False, "skip_rows": 0, "colunas": [ "ID_PROPOSTA", "UF_PROPONENTE", "MUNIC_PROPONENTE", "COD_MUNIC_IBGE", @@ -11,6 +12,9 @@ "DIA_PROP", "MES_PROP", "ANO_PROP", "DIA_PROPOSTA", "COD_ORGAO", "DESC_ORGAO", "MODALIDADE", "IDENTIF_PROPONENTE", "NM_PROPONENTE", "CEP_PROPONENTE", "ENDERECO_PROPONENTE", "BAIRRO_PROPONENTE", "NM_BANCO", + "SITUACAO_CONTA", "SITUACAO_PROJETO_BASICO", "SIT_PROPOSTA", + "DIA_INIC_VIGENCIA_PROPOSTA", "DIA_FIM_VIGENCIA_PROPOSTA", + "OBJETO_PROPOSTA", "ITEM_INVESTIMENTO", "ENVIADA_MANDATARIA", "NOME_SUBTIPO_PROPOSTA", "DESCRICAO_SUBTIPO_PROPOSTA", "VL_GLOBAL_PROP", "VL_REPASSE_PROP", "VL_CONTRAPARTIDA_PROP", "CD_AGENCIA", "CD_CONTA", ], @@ -20,6 +24,7 @@ "nome_csv": "siconv_convenio.csv", "conflict_fields": ["nr_convenio"], "primary_key": ["nr_convenio"], + "truncate_before_insert": False, "skip_rows": 0, "colunas": [ "NR_CONVENIO", "ID_PROPOSTA", "DIA", "MES", "ANO", "DIA_ASSIN_CONV", @@ -35,4 +40,185 @@ "VL_SALDO_CONTA", "VALOR_GLOBAL_ORIGINAL_CONV", ], }, + { + "nome_tabela": "desembolso", + "nome_csv": "siconv_desembolso.csv", + "conflict_fields": ["id_desembolso"], + "primary_key": ["id_desembolso"], + "truncate_before_insert": False, + "skip_rows": 0, + "colunas": [ + "ID_DESEMBOLSO", "NR_CONVENIO", "DT_ULT_DESEMBOLSO", "QTD_DIAS_SEM_DESEMBOLSO", + "DATA_DESEMBOLSO", "ANO_DESEMBOLSO", "MES_DESEMBOLSO", "NR_SIAFI", + "UG_EMITENTE_DH", "OBSERVACAO_DH", "VL_DESEMBOLSADO", + ], + }, + { + "nome_tabela": "desbloqueio", + "nome_csv": "siconv_desbloqueio_cr.csv", + "conflict_fields": [], + "primary_key": [], + "truncate_before_insert": True, + "skip_rows": 0, + "colunas": [ + "NR_CONVENIO", "NR_OB", "DATA_CADASTRO", "DATA_ENVIO", + "TIPO_RECURSO_DESBLOQUEIO", "VL_TOTAL_DESBLOQUEIO", + "VL_DESBLOQUEADO", "VL_BLOQUEADO", + ], + }, + { + "nome_tabela": "solicitacao_alteracao", + "nome_csv": "siconv_solicitacao_alteracao.csv", + "conflict_fields": ["id_solicitacao"], + "primary_key": ["id_solicitacao"], + "truncate_before_insert": False, + "skip_rows": 0, + "colunas": [ + "ID_SOLICITACAO", "NR_CONVENIO", "NR_SOLICITACAO", "SITUACAO_SOLICITACAO", + "OBJETO_SOLICITACAO", "DATA_SOLICITACAO", + ], + }, + { + "nome_tabela": "termo_aditivo", + "nome_csv": "siconv_termo_aditivo.csv", + "conflict_fields": ["nr_convenio", "numero_ta"], + "primary_key": ["nr_convenio", "numero_ta"], + "truncate_before_insert": False, + "skip_rows": 0, + "colunas": [ + "NR_CONVENIO", "ID_SOLICITACAO", "NUMERO_TA", "TIPO_TA", + "VL_GLOBAL_TA", "VL_REPASSE_TA", "VL_CONTRAPARTIDA_TA", + "DT_ASSINATURA_TA", "DT_INICIO_TA", "DT_FIM_TA", "JUSTIFICATIVA_TA", + ], + }, + { + "nome_tabela": "solicitacao_rendimento_aplicacao", + "nome_csv": "siconv_solicitacao_rendimento_aplicacao.csv", + "conflict_fields": ["id_solicitacao_rend_aplicacao"], + "primary_key": ["id_solicitacao_rend_aplicacao"], + "truncate_before_insert": False, + "skip_rows": 0, + "colunas": [ + "ID_SOLICITACAO_REND_APLICACAO", "NR_CONVENIO", "NR_SOLICITACAO_REND_APLICACAO", + "STATUS_SOLICITACAO_REND_APLICACAO", "DATA_SOLICITACAO_REND_APLICACAO", + "VALOR_SOLICITACAO_REND_APLICACAO", "VALOR_APROVADO_SOLICITACAO_REND_APLICACAO", + ], + }, + { + "nome_tabela": "prorroga_oficio", + "nome_csv": "siconv_prorroga_oficio.csv", + "conflict_fields": [], + "primary_key": [], + "truncate_before_insert": True, + "skip_rows": 0, + "colunas": [ + "NR_CONVENIO", "NR_PRORROGA", "DT_INICIO_PRORROGA", "DT_FIM_PRORROGA", + "DIAS_PRORROGA", "DT_ASSINATURA_PRORROGA", "SIT_PRORROGA", + ], + }, + { + "nome_tabela": "pagamento_tributo", + "nome_csv": "siconv_pagamento_tributo.csv", + "conflict_fields": ["nr_convenio", "data_tributo"], + "primary_key": ["nr_convenio", "data_tributo"], + "truncate_before_insert": False, + "skip_rows": 0, + "colunas": [ + "NR_CONVENIO", "DATA_TRIBUTO", "VL_PAG_TRIBUTOS", + ], + }, + { + "nome_tabela": "pagamento", + "nome_csv": "siconv_pagamento.csv", + "conflict_fields": ["nr_mov_fin"], + "primary_key": ["nr_mov_fin"], + "truncate_before_insert": False, + "skip_rows": 0, + "colunas": [ + "NR_MOV_FIN", "NR_CONVENIO", "IDENTIF_FORNECEDOR", "NOME_FORNECEDOR", + "TP_MOV_FINANCEIRA", "DATA_PAG", "NR_DL", "DESC_DL", + "VL_PAGO", "ID_DL", "DATA_EMISSAO_DL", + ], + }, + { + "nome_tabela": "licitacao", + "nome_csv": "siconv_licitacao.csv", + "conflict_fields": ["id_licitacao"], + "primary_key": ["id_licitacao"], + "truncate_before_insert": False, + "skip_rows": 0, + "colunas": [ + "ID_LICITACAO", "NR_CONVENIO", "NR_LICITACAO", "MODALIDADE_LICITACAO", + "TP_PROCESSO_COMPRA", "TIPO_LICITACAO", "NR_PROCESSO_LICITACAO", + "DATA_PUBLICACAO_LICITACAO", "DATA_ABERTURA_LICITACAO", "DATA_ENCERRAMENTO_LICITACAO", + "DATA_HOMOLOGACAO_LICITACAO", "STATUS_LICITACAO", "SITUACAO_ACEITE_PROCESSO_EXECU", + "SISTEMA_ORIGEM", "SITUACAO_SISTEMA", "VALOR_LICITACAO", + "DATA_ANALISE_ACEITE", "DATA_ENVIO_ANALISE", + ], + }, + { + "nome_tabela": "ingresso_contrapartida", + "nome_csv": "siconv_ingresso_contrapartida.csv", + "conflict_fields": ["nr_convenio", "dt_ingresso_contrapartida"], + "primary_key": ["nr_convenio", "dt_ingresso_contrapartida"], + "truncate_before_insert": False, + "skip_rows": 0, + "colunas": [ + "NR_CONVENIO", "DT_INGRESSO_CONTRAPARTIDA", "VL_INGRESSO_CONTRAPARTIDA", + ], + }, + { + "nome_tabela": "empenho", + "nome_csv": "siconv_empenho.csv", + "conflict_fields": [], + "primary_key": [], + "truncate_before_insert": True, + "skip_rows": 0, + "colunas": [ + "ID_EMPENHO", "NR_CONVENIO", "NR_EMPENHO", "TIPO_NOTA", "DESC_TIPO_NOTA", + "DATA_EMISSAO", "COD_SITUACAO_EMPENHO", "DESC_SITUACAO_EMPENHO", + "UG_EMITENTE", "UG_RESPONSAVEL", "FONTE_RECURSO", "NATUREZA_DESPESA", + "PLANO_INTERNO", "PTRES", "VALOR_EMPENHO", "RESULTADO_PRIMARIO", + "OBSERVACAO_EMPENHO", "DESCRICAO_EMENDA_SIAFI", + ], + }, + { + "nome_tabela": "historico_situacao", + "nome_csv": "siconv_historico_situacao.csv", + "conflict_fields": [], + "primary_key": [], + "truncate_before_insert": True, + "skip_rows": 0, + "colunas": [ + "ID_PROPOSTA", "NR_CONVENIO", "DIA_HISTORICO_SIT", "HISTORICO_SIT", + "DIAS_HISTORICO_SIT", "COD_HISTORICO_SIT", + ], + }, + { + "nome_tabela": "cronograma_desembolso", + "nome_csv": "siconv_cronograma_desembolso.csv", + "conflict_fields": ["id_proposta", "nr_convenio", "nr_parcela_crono_desembolso"], + "primary_key": ["id_proposta", "nr_convenio", "nr_parcela_crono_desembolso"], + "truncate_before_insert": False, + "skip_rows": 0, + "colunas": [ + "ID_PROPOSTA", "NR_CONVENIO", "NR_PARCELA_CRONO_DESEMBOLSO", + "MES_CRONO_DESEMBOLSO", "ANO_CRONO_DESEMBOLSO", "TIPO_RESP_CRONO_DESEMBOLSO", + "VALOR_PARCELA_CRONO_DESEMBOLSO", + ], + }, + { + "nome_tabela": "meta_crono_fisico", + "nome_csv": "siconv_meta_crono_fisico.csv", + "conflict_fields": ["id_meta"], + "primary_key": ["id_meta"], + "truncate_before_insert": False, + "skip_rows": 0, + "colunas": [ + "ID_META", "ID_PROPOSTA", "NR_CONVENIO", "COD_PROGRAMA", "NOME_PROGRAMA", + "NR_META", "TIPO_META", "DESC_META", "DATA_INICIO_META", "DATA_FIM_META", + "UF_META", "MUNICIPIO_META", "ENDERECO_META", "CEP_META", + "QTD_META", "UND_FORNECIMENTO_META", "VL_META", + ], + }, ] \ No newline at end of file From 324e8faee7261fe97be057134df4324bce390a8e Mon Sep 17 00:00:00 2001 From: Luana Date: Tue, 14 Apr 2026 09:54:27 -0300 Subject: [PATCH 262/317] =?UTF-8?q?fix:=20move=20zip=20do=20siconv=20para?= =?UTF-8?q?=20diret=C3=B3rio=20tempor=C3=A1rio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- airflow_lappis/plugins/cliente_siconv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airflow_lappis/plugins/cliente_siconv.py b/airflow_lappis/plugins/cliente_siconv.py index 0921b28f..56b672bd 100644 --- a/airflow_lappis/plugins/cliente_siconv.py +++ b/airflow_lappis/plugins/cliente_siconv.py @@ -6,7 +6,7 @@ class ClienteSiconv: URL_ZIP = "https://repositorio.dados.gov.br/seges/detru/siconv.zip" - ZIP_PATH = "/opt/airflow/dags/data_ingest/siconv/siconv.zip" + ZIP_PATH = "/tmp/siconv.zip" def baixar_zip(self) -> None: logging.info("[cliente_siconv.py] Baixando arquivo SICONV...") From 854b6508a73af45ac9b4b720a4a08636461615e6 Mon Sep 17 00:00:00 2001 From: Luana Date: Tue, 14 Apr 2026 10:20:11 -0300 Subject: [PATCH 263/317] =?UTF-8?q?feat:=20adiciona=20task=20de=20limpeza?= =?UTF-8?q?=20do=20zip=20ap=C3=B3s=20ingest=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dags/data_ingest/siconv/sincov_ingest_dag.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py b/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py index 0aa2692b..ddab5ae8 100644 --- a/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py @@ -7,7 +7,7 @@ from tabelas_siconv import TABELAS_SICONV @dag( - schedule_interval="@daily", + schedule_interval= None, start_date=datetime(2024, 1, 1), catchup=False, default_args={ @@ -15,7 +15,7 @@ "retries": 1, "retry_delay": timedelta(minutes=5), }, - tags=["siconv", "data_ingest"], + tags=["siconv", "MIR"], ) def siconv_ingestao_dag() -> None: @@ -78,6 +78,15 @@ def ingerir_tabela(zip_path: str, nome_tabela: str, nome_csv: str, conflict_fiel logging.warning(f"[siconv_ingest_dag.py] Nenhum registro processado para {nome_tabela}") else: logging.info(f"[siconv_ingest_dag.py] Ingestão finalizada: {total_inserido} registros em {nome_tabela}") + + @task + def deletar_zip(zip_path: str) -> None: + import os + if os.path.exists(zip_path): + os.remove(zip_path) + logging.info(f"[siconv_ingest_dag.py] Arquivo {zip_path} deletado com sucesso") + else: + logging.warning(f"[siconv_ingest_dag.py] Arquivo {zip_path} não encontrado para deletar") path_zip = baixar_siconv() @@ -97,5 +106,6 @@ def ingerir_tabela(zip_path: str, nome_tabela: str, nome_csv: str, conflict_fiel ultima_task >> task_atual ultima_task = task_atual + ultima_task >> deletar_zip(path_zip) siconv_ingestao_dag() \ No newline at end of file From 3912cfd5cf3785bb6d562ac37ada079e72863822 Mon Sep 17 00:00:00 2001 From: Mateus de Castro <140627829+mat054@users.noreply.github.com> Date: Tue, 14 Apr 2026 18:59:49 +0000 Subject: [PATCH 264/317] feat: ingest tabels ipea pro (#200) --- .../ipea_pro_to_postgres_ingest_dag.py | 91 +++++++++++++++++++ airflow_lappis/plugins/cliente_sqlserver.py | 19 +++- 2 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 airflow_lappis/dags/data_ingest/ipea_pro/ipea_pro_to_postgres_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/ipea_pro/ipea_pro_to_postgres_ingest_dag.py b/airflow_lappis/dags/data_ingest/ipea_pro/ipea_pro_to_postgres_ingest_dag.py new file mode 100644 index 00000000..669e8e92 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/ipea_pro/ipea_pro_to_postgres_ingest_dag.py @@ -0,0 +1,91 @@ +import logging +from datetime import datetime, timedelta +from typing import Dict, List, TypedDict + +from airflow.decorators import dag, task +from airflow.models import Variable +from cliente_postgres import ClientPostgresDB +from cliente_sqlserver import ClientSQLServerDB +from postgres_helpers import get_postgres_conn +from schedule_loader import get_dynamic_schedule + + +class SQLServerTableConfig(TypedDict): + source_schema: str + source_table: str + target_schema: str + target_table: str + primary_key: List[str] + + +TABLES_TO_SYNC_VARIABLE = "ipea_pro_tables_to_sync" + + +def _load_tables_from_variable() -> List[SQLServerTableConfig]: + return Variable.get( + TABLES_TO_SYNC_VARIABLE, + default_var=[], + deserialize_json=True, + ) + + +@dag( + schedule_interval=get_dynamic_schedule("ipea_pro_to_postgres_ingest_dag"), + start_date=datetime(2024, 1, 1), + catchup=False, + max_active_runs=1, + default_args={ + "owner": "Davi", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["sql_server", "postgres", "ipea_pro"], +) +def sql_server_to_postgres_ingest_dag() -> None: + """Replica tabelas do Ipea Pro para o Postgres Analytics.""" + + @task + def replicate_table(table_cfg: SQLServerTableConfig) -> Dict[str, int]: + sql_server = ClientSQLServerDB("ipeapro") + postgres = ClientPostgresDB(get_postgres_conn()) + source_schema = table_cfg["source_schema"] + source_table = table_cfg["source_table"] + target_schema = table_cfg["target_schema"] + target_table = table_cfg["target_table"] + primary_key = table_cfg["primary_key"] + + source_name = f"{source_schema}.{source_table}" + + logging.info( + "[ipea_pro_to_postgres_ingest_dag.py] Iniciando replicacao de %s", + source_name, + ) + + rows = sql_server.fetch_table_all( + schema=source_schema, + table_name=source_table, + ) + + if rows: + postgres.insert_data( + data=rows, + table_name=target_table, + conflict_fields=primary_key, + primary_key=primary_key, + schema=target_schema, + ) + + logging.info( + "[ipea_pro_to_postgres_ingest_dag.py] Replicacao concluida " + "para %s. Registros processados=%s", + source_name, + len(rows), + ) + + return {source_name: len(rows)} + + tables_to_sync = _load_tables_from_variable() + replicate_table.expand(table_cfg=tables_to_sync) + + +sql_server_to_postgres_ingest_dag() diff --git a/airflow_lappis/plugins/cliente_sqlserver.py b/airflow_lappis/plugins/cliente_sqlserver.py index b779ff01..caf3352d 100644 --- a/airflow_lappis/plugins/cliente_sqlserver.py +++ b/airflow_lappis/plugins/cliente_sqlserver.py @@ -15,6 +15,17 @@ def __init__(self, mssql_conn_id: str = "mssql_default") -> None: f"mssql_conn_id={mssql_conn_id}" ) + @staticmethod + def _sanitize_value(value: Any) -> Any: + """Normaliza valores vindos do SQL Server para tipos seguros.""" + if value is None: + return None + + if str(value) == "NaT": + return None + + return value + def fetch_table_all( self, schema: str, @@ -42,7 +53,13 @@ def fetch_table_all( return [] columns = [description[0] for description in cursor.description] - records = [dict(zip(columns, row)) for row in rows] + records = [ + { + column: self._sanitize_value(value) + for column, value in zip(columns, row) + } + for row in rows + ] logging.info( "[cliente_sqlserver.py] Query executed successfully, fetched %s rows " From 08ca70b612d3d2038aba89e0fa3248c185d6bb95 Mon Sep 17 00:00:00 2001 From: Mateushqms Date: Wed, 15 Apr 2026 12:30:45 -0300 Subject: [PATCH 265/317] feat: camada bronze para as notas de credito do transfere gov --- .../bronze/notas_de_credito.sql | 23 ++ .../bronze/planos_acao_ted.sql | 31 +++ .../empenhos_ted_dbt/bronze/programas_ted.sql | 39 ++++ .../empenhos_ted_dbt/bronze/schema.yaml | 207 +++++++++++++++++- .../dags/dbt/mir/models/sources.yml | 3 + 5 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/notas_de_credito.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/planos_acao_ted.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/programas_ted.sql diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/notas_de_credito.sql b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/notas_de_credito.sql new file mode 100644 index 00000000..3b0f4601 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/notas_de_credito.sql @@ -0,0 +1,23 @@ +{{ config(materialized="table") }} + + +with + notas_de_credito_raw as ( + select + id_nota::integer as id_nota, + id_plano_acao::integer as id_plano_acao, + tx_minuta_nota::text as tx_minuta_nota, + tx_numero_nota::text as tx_numero_nota, + dt_emissao_nota::timestamp as dt_emissao_nota, + cd_gestao_emitente_nota::text as cd_gestao_emitente_nota, + cd_gestao_favorecida_nota::text as cd_gestao_favorecida_nota, + tx_situacao_nota::text as tx_situacao_nota, + cd_ug_emitente_nota::text as cd_ug_emitente_nota, + cd_ug_favorecida_nota::text as cd_ug_favorecida_nota, + tx_observacao_nota::text as tx_observacao_nota, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("transfere_gov", "notas_de_credito") }} + ) + +select * +from notas_de_credito_raw diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/planos_acao_ted.sql b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/planos_acao_ted.sql new file mode 100644 index 00000000..fda249e3 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/planos_acao_ted.sql @@ -0,0 +1,31 @@ +{{ config(materialized="table") }} + +with + planos_acao_raw as ( + select + id_plano_acao::integer as id_plano_acao, + id_programa::integer as id_programa, + sigla_unidade_descentralizada::text as sigla_unidade_descentralizada, + unidade_descentralizada::text as unidade_descentralizada, + sigla_unidade_responsavel_execucao::text as sigla_unidade_responsavel_execucao, + unidade_responsavel_execucao::text as unidade_responsavel_execucao, + nullif(vl_total_plano_acao, '')::numeric(15, 2) as vl_total_plano_acao, + nullif(dt_inicio_vigencia, '')::timestamp::date as dt_inicio_vigencia, + nullif(dt_fim_vigencia, '')::timestamp::date as dt_fim_vigencia, + tx_objeto_plano_acao::text as tx_objeto_plano_acao, + tx_justificativa_plano_acao::text as tx_justificativa_plano_acao, + nullif(in_forma_execucao_direta, '')::boolean as in_forma_execucao_direta, + nullif(in_forma_execucao_particulares, '')::boolean as in_forma_execucao_particulares, + nullif(in_forma_execucao_descentralizada, '')::boolean as in_forma_execucao_descentralizada, + tx_situacao_plano_acao::text as tx_situacao_plano_acao, + nullif(aa_ano_plano_acao, '')::integer as aa_ano_plano_acao, + nullif(vl_beneficiario_especifico, '')::numeric(15, 2) as vl_beneficiario_especifico, + nullif(vl_chamamento_publico, '')::numeric(15, 2) as vl_chamamento_publico, + sq_instrumento::text as sq_instrumento, + nullif(aa_instrumento, '')::integer as aa_instrumento, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("transfere_gov", "planos_acao") }} + ) + +select * +from planos_acao_raw diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/programas_ted.sql b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/programas_ted.sql new file mode 100644 index 00000000..ae9319b4 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/programas_ted.sql @@ -0,0 +1,39 @@ +{{ config(materialized="table") }} + +with + programas_raw as ( + select + id_programa::integer as id_programa, + tx_codigo_programa::text as tx_codigo_programa, + nullif(aa_ano_programa, '')::integer as aa_ano_programa, + tx_situacao_programa::text as tx_situacao_programa, + tx_nome_programa::text as tx_nome_programa, + sigla_unidade_descentralizadora::text as sigla_unidade_descentralizadora, + unidade_descentralizadora::text as unidade_descentralizadora, + sigla_unidade_responsavel_acompanhamento::text as sigla_unidade_responsavel_acompanhamento, + unidade_responsavel_acompanhamento::text as unidade_responsavel_acompanhamento, + tx_nome_institucional_programa::text as tx_nome_institucional_programa, + tx_objetivo_programa::text as tx_objetivo_programa, + tx_descricao_programa::text as tx_descricao_programa, + nullif(in_grupo_investimento_obra, '')::boolean as in_grupo_investimento_obra, + nullif(in_grupo_investimento_servico, '')::boolean as in_grupo_investimento_servico, + nullif(in_grupo_investimento_equipamento, '')::boolean as in_grupo_investimento_equipamento, + in_autoriza_subdescentralizacao_outro::text as in_autoriza_subdescentralizacao_outro, + in_autoriza_realizacao_despesas::text as in_autoriza_realizacao_despesas, + in_autoriza_execucao_creditos_descentralizada::text as in_autoriza_execucao_creditos_descentralizada, + nullif(in_beneficiario_especifico, '')::boolean as in_beneficiario_especifico, + nullif(dt_recebimento_plano_beneficiario_inicio, '')::timestamp::date + as dt_recebimento_plano_beneficiario_inicio, + nullif(dt_recebimento_plano_beneficiario_fim, '')::timestamp::date + as dt_recebimento_plano_beneficiario_fim, + nullif(in_chamamento_publico, '')::boolean as in_chamamento_publico, + nullif(dt_recebimento_plano_chamamento_inicio, '')::timestamp::date + as dt_recebimento_plano_chamamento_inicio, + nullif(dt_recebimento_plano_chamamento_fim, '')::timestamp::date + as dt_recebimento_plano_chamamento_fim, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("transfere_gov", "programas") }} + ) + +select * +from programas_raw diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/schema.yaml b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/schema.yaml index 2af4580a..dc4d570b 100644 --- a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/schema.yaml +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/schema.yaml @@ -364,4 +364,209 @@ models: - verificacao_tipagem: nome_tabela: 'siafi_dbt.pf_transfere_mir' nome_coluna: 'id_programacao' - tipo_esperado: 'integer' \ No newline at end of file + tipo_esperado: 'integer' + + - name: notas_de_credito + description: > + Modelo da camada bronze para Notas de Crédito do TransfereGov (módulo TED), + com padronização de tipos e ajuste do timestamp de ingestão para UTC-3. + meta: + tags: + - bronze + columns: + - name: id_nota + description: "Identificador único da Nota de Crédito." + - name: id_plano_acao + description: "Identificador único do Plano de Ação associado." + - name: tx_minuta_nota + description: "Minuta/identificador textual da Nota de Crédito." + - name: tx_numero_nota + description: "Número da Nota de Crédito." + - name: dt_emissao_nota + description: "Data/hora de emissão da Nota de Crédito." + - name: cd_gestao_emitente_nota + description: "Código da gestão emitente da Nota de Crédito." + - name: cd_gestao_favorecida_nota + description: "Código da gestão favorecida da Nota de Crédito." + - name: tx_situacao_nota + description: "Situação da Nota de Crédito." + - name: cd_ug_emitente_nota + description: "Código da Unidade Gestora emitente da Nota de Crédito." + - name: cd_ug_favorecida_nota + description: "Código da Unidade Gestora favorecida da Nota de Crédito." + - name: tx_observacao_nota + description: "Observações/texto livre da Nota de Crédito." + - name: dt_ingest + description: "Timestamp de ingestão ajustado para UTC-3." + + tests: + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.notas_de_credito_mir' + nome_coluna: 'id_nota' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.notas_de_credito_mir' + nome_coluna: 'id_plano_acao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.notas_de_credito_mir' + nome_coluna: 'dt_emissao_nota' + tipo_esperado: 'timestamp without time zone' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.notas_de_credito_mir' + nome_coluna: 'dt_ingest' + tipo_esperado: 'timestamp with time zone' + + - name: programas_ted + description: > + Modelo da camada bronze para Programas do TransfereGov (módulo TED), + com padronização de tipos e ajuste do timestamp de ingestão para UTC-3. + meta: + tags: + - bronze + columns: + - name: id_programa + description: "Identificador único do Programa." + - name: tx_codigo_programa + description: "Código do Programa." + - name: aa_ano_programa + description: "Ano do Programa." + - name: tx_situacao_programa + description: "Situação do Programa." + - name: tx_nome_programa + description: "Nome do Programa." + - name: sigla_unidade_descentralizadora + description: "Sigla da unidade descentralizadora." + - name: unidade_descentralizadora + description: "Unidade descentralizadora." + - name: sigla_unidade_responsavel_acompanhamento + description: "Sigla da unidade responsável pelo acompanhamento." + - name: unidade_responsavel_acompanhamento + description: "Unidade responsável pelo acompanhamento." + - name: tx_nome_institucional_programa + description: "Nome institucional do Programa." + - name: tx_objetivo_programa + description: "Objetivo do Programa." + - name: tx_descricao_programa + description: "Descrição do Programa." + - name: in_grupo_investimento_obra + description: "Indicador do grupo de investimento (obra)." + - name: in_grupo_investimento_servico + description: "Indicador do grupo de investimento (serviço)." + - name: in_grupo_investimento_equipamento + description: "Indicador do grupo de investimento (equipamento)." + - name: in_autoriza_subdescentralizacao_outro + description: "Indicador/flag de autorização de subdescentralização (outro)." + - name: in_autoriza_realizacao_despesas + description: "Indicador/flag de autorização para realização de despesas." + - name: in_autoriza_execucao_creditos_descentralizada + description: "Indicador/flag de autorização para execução de créditos descentralizada." + - name: in_beneficiario_especifico + description: "Indicador de beneficiário específico." + - name: dt_recebimento_plano_beneficiario_inicio + description: "Data de início do recebimento do plano de beneficiário." + - name: dt_recebimento_plano_beneficiario_fim + description: "Data final do recebimento do plano de beneficiário." + - name: in_chamamento_publico + description: "Indicador de chamamento público." + - name: dt_recebimento_plano_chamamento_inicio + description: "Data de início do recebimento do plano de chamamento." + - name: dt_recebimento_plano_chamamento_fim + description: "Data final do recebimento do plano de chamamento." + - name: dt_ingest + description: "Timestamp de ingestão ajustado para UTC-3." + + tests: + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.programas_mir' + nome_coluna: 'id_programa' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.programas_mir' + nome_coluna: 'aa_ano_programa' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.programas_mir' + nome_coluna: 'in_beneficiario_especifico' + tipo_esperado: 'boolean' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.programas_mir' + nome_coluna: 'dt_recebimento_plano_beneficiario_inicio' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.programas_mir' + nome_coluna: 'dt_ingest' + tipo_esperado: 'timestamp with time zone' + + - name: planos_acao_ted + description: > + Modelo da camada bronze para Planos de Ação do TransfereGov (módulo TED), + com padronização de tipos e ajuste do timestamp de ingestão para UTC-3. + meta: + tags: + - bronze + columns: + - name: id_plano_acao + description: "Identificador único do Plano de Ação." + - name: id_programa + description: "Identificador do Programa associado." + - name: sigla_unidade_descentralizada + description: "Sigla da unidade descentralizada." + - name: unidade_descentralizada + description: "Unidade descentralizada." + - name: sigla_unidade_responsavel_execucao + description: "Sigla da unidade responsável pela execução." + - name: unidade_responsavel_execucao + description: "Unidade responsável pela execução." + - name: vl_total_plano_acao + description: "Valor total do Plano de Ação." + - name: dt_inicio_vigencia + description: "Data de início de vigência do Plano de Ação." + - name: dt_fim_vigencia + description: "Data de fim de vigência do Plano de Ação." + - name: tx_objeto_plano_acao + description: "Objeto do Plano de Ação." + - name: tx_justificativa_plano_acao + description: "Justificativa do Plano de Ação." + - name: in_forma_execucao_direta + description: "Indicador da forma de execução direta." + - name: in_forma_execucao_particulares + description: "Indicador da forma de execução por particulares." + - name: in_forma_execucao_descentralizada + description: "Indicador da forma de execução descentralizada." + - name: tx_situacao_plano_acao + description: "Situação do Plano de Ação." + - name: aa_ano_plano_acao + description: "Ano do Plano de Ação." + - name: vl_beneficiario_especifico + description: "Valor do beneficiário específico." + - name: vl_chamamento_publico + description: "Valor do chamamento público." + - name: sq_instrumento + description: "Sequencial do instrumento." + - name: aa_instrumento + description: "Ano do instrumento." + - name: dt_ingest + description: "Timestamp de ingestão ajustado para UTC-3." + + tests: + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.planos_acao_mir' + nome_coluna: 'id_plano_acao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.planos_acao_mir' + nome_coluna: 'id_programa' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.planos_acao_mir' + nome_coluna: 'vl_total_plano_acao' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.planos_acao_mir' + nome_coluna: 'dt_inicio_vigencia' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.planos_acao_mir' + nome_coluna: 'dt_ingest' + tipo_esperado: 'timestamp with time zone' diff --git a/airflow_lappis/dags/dbt/mir/models/sources.yml b/airflow_lappis/dags/dbt/mir/models/sources.yml index 8c9f11eb..d344ed3d 100644 --- a/airflow_lappis/dags/dbt/mir/models/sources.yml +++ b/airflow_lappis/dags/dbt/mir/models/sources.yml @@ -30,7 +30,10 @@ sources: - name: transfere_gov schema: transfere_gov tables: + - name: programas + - name: planos_acao - name: programacao_financeira + - name: notas_de_credito - name: siafi schema: siafi From 12b432f84704e28f09aeb53bfe21533e06895fbb Mon Sep 17 00:00:00 2001 From: Mateus de Castro <140627829+mat054@users.noreply.github.com> Date: Wed, 15 Apr 2026 18:38:46 +0000 Subject: [PATCH 266/317] Feat/ingest ipea pro (#201) * fix: nomenclatura dag * fix: ajuste dags --- .../data_ingest/ipea_pro/ipea_pro_to_postgres_ingest_dag.py | 6 +++--- .../sisbolsas/sisbolsas_to_postgres_ingest_dag.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/ipea_pro/ipea_pro_to_postgres_ingest_dag.py b/airflow_lappis/dags/data_ingest/ipea_pro/ipea_pro_to_postgres_ingest_dag.py index 669e8e92..1150b64a 100644 --- a/airflow_lappis/dags/data_ingest/ipea_pro/ipea_pro_to_postgres_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/ipea_pro/ipea_pro_to_postgres_ingest_dag.py @@ -35,13 +35,13 @@ def _load_tables_from_variable() -> List[SQLServerTableConfig]: catchup=False, max_active_runs=1, default_args={ - "owner": "Davi", + "owner": "Mateus", "retries": 1, "retry_delay": timedelta(minutes=5), }, tags=["sql_server", "postgres", "ipea_pro"], ) -def sql_server_to_postgres_ingest_dag() -> None: +def sql_server_to_postgres_ingest_dag_ipea_pro() -> None: """Replica tabelas do Ipea Pro para o Postgres Analytics.""" @task @@ -88,4 +88,4 @@ def replicate_table(table_cfg: SQLServerTableConfig) -> Dict[str, int]: replicate_table.expand(table_cfg=tables_to_sync) -sql_server_to_postgres_ingest_dag() +sql_server_to_postgres_ingest_dag_ipea_pro() diff --git a/airflow_lappis/dags/data_ingest/sisbolsas/sisbolsas_to_postgres_ingest_dag.py b/airflow_lappis/dags/data_ingest/sisbolsas/sisbolsas_to_postgres_ingest_dag.py index a43ce269..5b4f1d66 100644 --- a/airflow_lappis/dags/data_ingest/sisbolsas/sisbolsas_to_postgres_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/sisbolsas/sisbolsas_to_postgres_ingest_dag.py @@ -40,7 +40,7 @@ def _load_tables_from_variable() -> List[SQLServerTableConfig]: }, tags=["sql_server", "postgres", "sisbolsas"], ) -def sql_server_to_postgres_ingest_dag() -> None: +def sql_server_to_postgres_ingest_dag_sisbolsas() -> None: """Replica tabelas do SisBolsas para o Postgres Analytics.""" @task @@ -87,4 +87,4 @@ def replicate_table(table_cfg: SQLServerTableConfig) -> Dict[str, int]: replicate_table.expand(table_cfg=tables_to_sync) -sql_server_to_postgres_ingest_dag() +sql_server_to_postgres_ingest_dag_sisbolsas() From c5de5ae36ac0bd91a7182044f557db62355a7b09 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Thu, 16 Apr 2026 11:02:45 -0300 Subject: [PATCH 267/317] feat: camada silver pf_tg pa_tesouro --- .../silver/pf_unificado_planos_acao.sql | 96 +++++++++++++++++++ .../models/empenhos_ted_dbt/silver/schema.yml | 44 +++++++++ 2 files changed, 140 insertions(+) create mode 100644 airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/pf_unificado_planos_acao.sql diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/pf_unificado_planos_acao.sql b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/pf_unificado_planos_acao.sql new file mode 100644 index 00000000..8f68c70e --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/pf_unificado_planos_acao.sql @@ -0,0 +1,96 @@ +{{ config(materialized="table") }} + +with + pf_unificado as ( + select * + from {{ ref("pf_unificado") }} + ), + planos_acao_deduplicado as ( + select + id_plano_acao, + id_programa, + sigla_unidade_descentralizada, + unidade_descentralizada, + sigla_unidade_responsavel_execucao, + unidade_responsavel_execucao, + vl_total_plano_acao, + dt_inicio_vigencia, + dt_fim_vigencia, + tx_objeto_plano_acao, + tx_justificativa_plano_acao, + in_forma_execucao_direta, + in_forma_execucao_particulares, + in_forma_execucao_descentralizada, + tx_situacao_plano_acao, + aa_ano_plano_acao, + vl_beneficiario_especifico, + vl_chamamento_publico, + sq_instrumento, + aa_instrumento, + dt_ingest as dt_ingest_plano_acao + from ( + select + pa.*, + row_number() over ( + partition by pa.id_plano_acao + order by pa.dt_ingest desc + ) as rn + from {{ ref("planos_acao_ted") }} pa + ) pa_filtrado + where rn = 1 + ) + +select + pf.emissao_mes, + pf.emissao_dia, + pf.ug_emitente, + pf.ug_emitente_descricao, + pf.ug_favorecido, + pf.ug_favorecido_descricao, + pf.pf_evento, + pf.pf_evento_descricao, + pf.pf, + pf.pf_inscricao, + pf.pf_acao, + pf.pf_acao_descricao, + pf.pf_fonte_recursos, + pf.pf_fonte_recursos_descricao, + pf.doc_observacao, + pf.pf_valor_linha, + pf.id_programacao, + pf.id_plano_acao, + pf.tp_pf_tipo_programacao, + pf.tx_minuta_programacao, + pf.tx_numero_programacao, + pf.tx_situacao_programacao, + pf.tx_observacao_programacao, + pf.ug_emitente_programacao, + pf.ug_favorecida_programacao, + pf.dh_recebimento_programacao, + pf.pf_chave, + pa.id_programa, + pa.sigla_unidade_descentralizada, + pa.unidade_descentralizada, + pa.sigla_unidade_responsavel_execucao, + pa.unidade_responsavel_execucao, + pa.vl_total_plano_acao, + pa.dt_inicio_vigencia, + pa.dt_fim_vigencia, + pa.tx_objeto_plano_acao, + pa.tx_justificativa_plano_acao, + pa.in_forma_execucao_direta, + pa.in_forma_execucao_particulares, + pa.in_forma_execucao_descentralizada, + pa.tx_situacao_plano_acao, + pa.aa_ano_plano_acao, + pa.vl_beneficiario_especifico, + pa.vl_chamamento_publico, + pa.sq_instrumento, + pa.aa_instrumento, + coalesce( + greatest(pf.dt_ingest, pa.dt_ingest_plano_acao), + pf.dt_ingest, + pa.dt_ingest_plano_acao + ) as dt_ingest +from pf_unificado pf +left join planos_acao_deduplicado pa using (id_plano_acao) diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/schema.yml b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/schema.yml index 2b1d42f8..aa1f769d 100644 --- a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/schema.yml +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/schema.yml @@ -84,3 +84,47 @@ models: nome_tabela: 'siafi_dbt.pf_unificado' nome_coluna: 'id_plano_acao' tipo_esperado: 'integer' + + - name: pf_unificado_planos_acao + description: > + Tabela silver que enriquece a base pf_unificado com os dados de planos de acao + do Transferegov, por meio do id_plano_acao. O cruzamento preserva todas as + linhas de PF e deduplica planos de acao por id_plano_acao, mantendo o registro + mais recente por dt_ingest. + meta: + tags: + - silver + columns: + - name: id_programacao + description: "Identificador da programacao financeira no Transferegov." + - name: id_plano_acao + description: "Identificador do plano de acao usado como chave de cruzamento." + - name: id_programa + description: "Identificador do programa associado ao plano de acao." + - name: pf + description: "Identificador completo da programacao financeira no Tesouro." + - name: tx_numero_programacao + description: "Numero da programacao financeira no Transferegov." + - name: tx_situacao_plano_acao + description: "Situacao do plano de acao no Transferegov." + - name: vl_total_plano_acao + description: "Valor total informado para o plano de acao." + - name: dt_ingest + description: "Maior timestamp de ingestao entre pf_unificado e planos_acao_ted." + tests: + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.pf_unificado_planos_acao' + nome_coluna: 'id_programacao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.pf_unificado_planos_acao' + nome_coluna: 'id_plano_acao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.pf_unificado_planos_acao' + nome_coluna: 'id_programa' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.pf_unificado_planos_acao' + nome_coluna: 'vl_total_plano_acao' + tipo_esperado: 'numeric' From 1935b978f075fcd8dd52634b25fd71d405c5ddbf Mon Sep 17 00:00:00 2001 From: Mateushqms Date: Fri, 17 Apr 2026 12:42:02 -0300 Subject: [PATCH 268/317] feat: camada silver de teds-siafi e ajustes camada bronze siafi --- .../bronze/nc_tesouro_mir.sql | 107 +++++++------ .../empenhos_ted_dbt/bronze/schema.yaml | 145 ++++++++---------- .../empenhos_ted_dbt/silver/nc_unificado.sql | 67 ++++++++ .../models/empenhos_ted_dbt/silver/schema.yml | 95 ++++++++++++ 4 files changed, 281 insertions(+), 133 deletions(-) create mode 100644 airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/nc_unificado.sql diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/nc_tesouro_mir.sql b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/nc_tesouro_mir.sql index ba71d216..ae9f776c 100644 --- a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/nc_tesouro_mir.sql +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/nc_tesouro_mir.sql @@ -4,96 +4,93 @@ with notas_credito_pre as ( select - nc, - ptres, - nc_transferencia, - nc_ug_responsavel as ugr_codigo, - nc_ug_responsavel_descricao as ugr_nome, - nc_fonte_recursos as fonte_codigo, - nc_fonte_recursos_descricao as fonte_nome, - nc_plano_interno as pi_codigo, - nc_plano_interno_descricao1 as pi_nome, - favorecido_doc as favorecido_codigo, - favorecido_doc_descricao as favorecido_nome, - {{parse_financial_value("nc_valor_linha")}} as valor_celula, - (dt_ingest || '-03:00')::timestamptz as dt_ingest, programa_governo, programa_governo_descricao, acao_governo, acao_governo_descricao, - nc_evento, + + nc, + nc_transferencia, + nc_fonte_recursos, + nc_fonte_recursos_descricao, + ptres, nc_evento_descricao, + nc_ug_responsavel, + nc_ug_responsavel_descricao, nc_natureza_despesa, nc_natureza_despesa_descricao, - nc_plano_interno_descricao2, + nc_plano_interno, + nc_plano_interno_descricao1, + favorecido_doc, + favorecido_doc_descricao, + favorecido_municipio, favorecido_municipio_descricao, + + {{parse_financial_value("nc_valor_linha")}} as valor_celula, {{parse_financial_value("movimento_liquido_moeda_origem")}} as movimento_liquido_moeda_origem, + + (dt_ingest || '-03:00')::timestamptz as dt_ingest, + + cast(null as varchar) as descricao, + nc_plano_interno_descricao2, + nc_evento, + cast(null as varchar) as nc_item_detalhamento, cast(null as date) as emissao_dia, cast(null as varchar) as emissao_mes, cast(null as varchar) as emissao_ano, - cast(null as varchar) as dc, - cast(null as varchar) as emitente_codigo, - cast(null as varchar) as emitente_nome, - cast(null as varchar) as gnd_codigo, - cast(null as varchar) as gnd_nome, - cast(null as varchar) as descricao, - cast(null as varchar) as tipo_nc, - cast(null as varchar) as nc_item_detalhamento, cast(null as varchar) as ro, - 00::numeric(15,2) as item_total, - 00::numeric(15,2) as total_lista, + cast(null as varchar) as dc, + cast(null as numeric) as total_lista, cast(null as varchar) as esfera_orcamentaria_codigo, cast(null as varchar) as esfera_orcamentaria_nome from {{ source("siafi", "nc_tesouro_pre_2026") }} ), notas_credito_pos as ( select - nc, - ptres, - nc_transferencia, - ugr_codigo, - ugr_nome, - fonte_codigo, - fonte_nome, - pi_codigo, - pi_nome, - favorecido_codigo, - favorecido_nome, - {{parse_financial_value("valor_celula")}} as valor_celula, - (dt_ingest || '-03:00')::timestamptz as dt_ingest, + -- campos nulos: cast(null as varchar) as programa_governo, cast(null as varchar) as programa_governo_descricao, cast(null as varchar) as acao_governo, cast(null as varchar) as acao_governo_descricao, - cast(null as varchar) as nc_evento, - cast(null as varchar) as nc_evento_descricao, - cast(null as varchar) as nc_natureza_despesa, - cast(null as varchar) as nc_natureza_despesa_descricao, - cast(null as varchar) as nc_plano_interno_descricao2, + + nc, + nc_transferencia, + fonte_codigo as nc_fonte_recursos, + fonte_nome as nc_fonte_recursos_descricao, + ptres, + tipo_nc as nc_evento_descricao, + emitente_codigo as nc_ug_responsavel, + emitente_nome as nc_ug_responsavel_descricao, + gnd_codigo as nc_natureza_despesa, + gnd_nome as nc_natureza_despesa_descricao, + pi_codigo as nc_plano_interno, + pi_nome as nc_plano_interno_descricao1, + favorecido_codigo as nc_favorecido_doc, + favorecido_nome as nc_favorecido_doc_descricao, + cast(null as varchar) as favorecido_municipio, cast(null as varchar) as favorecido_municipio_descricao, - 00::numeric(15,2) as movimento_liquido_moeda_origem, + + {{parse_financial_value("valor_celula")}} as nc_valor_linha, + {{parse_financial_value("total_lista")}} as movimento_liquido_moeda_origem, + (dt_ingest || '-03:00')::timestamptz as dt_ingest, + + descricao, + cast(null as varchar)as nc_plano_interno_descricao2, + cast(null as varchar)as nc_evento, + nc_item_detalhamento, to_date(emissao_dia, 'DD/MM/YYYY') as emissao_dia, emissao_mes, emissao_ano, - dc, - emitente_codigo, - emitente_nome, - gnd_codigo, - gnd_nome, - descricao, - tipo_nc, - nc_item_detalhamento, ro, - {{parse_financial_value("item_total")}} as item_total, + dc, {{parse_financial_value("total_lista")}} as total_lista, esfera_orcamentaria_codigo, esfera_orcamentaria_nome - from {{ source("siafi", "nc_tesouro_pos__2026") }} ) select * from notas_credito_pre union all -select * from notas_credito_pos \ No newline at end of file +select * from notas_credito_pos diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/schema.yaml b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/schema.yaml index dc4d570b..e4b70074 100644 --- a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/schema.yaml +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/bronze/schema.yaml @@ -107,112 +107,101 @@ models: abrangendo os períodos anteriores e posteriores a 2026. Realiza a unificação (UNION ALL) das bases históricas e atuais, preenchendo com valores nulos (ou zero para campos financeiros) as colunas inexistentes em cada tabela original para garantir a integridade do schema. - O processamento inclui: - - Casting de tipos para texto e numérico; - - Limpeza de caracteres especiais em números de processo via Regex; - - Padronização de nomes de favorecidos para caixa alta; - - Conversão de valores financeiros para **numeric(15,2)** via macro; - - Filtro de segurança para processar apenas anos de emissão válidos (4 dígitos). + Também aplica o casamento semântico entre colunas equivalentes de 2025 e 2026 + (ex.: evento/tipo_nc, UG responsável/emitente, natureza/GND, plano interno/descrição), + mantendo um layout único para consumo nas camadas seguintes. meta: tags: - bronze columns: - - name: nc - description: "Número identificador da Nota de Crédito (NC)." - - name: ptres - description: "Programa de Trabalho Resumido (PTRES)." - - name: nc_transferencia - description: "Informações sobre transferência da Nota de Crédito." - - name: ugr_codigo - description: "Código da Unidade Gestora Responsável." - - name: ugr_nome - description: "Nome da Unidade Gestora Responsável." - - name: fonte_codigo - description: "Código da fonte de recursos." - - name: fonte_nome - description: "Descrição da fonte de recursos." - - name: pi_codigo - description: "Código do Plano Interno." - - name: pi_nome - description: "Descrição do Plano Interno." - - name: favorecido_codigo - description: "Identificador (CPF/CNPJ) do favorecido." - - name: favorecido_nome - description: "Nome ou razão social do favorecido." - - name: valor_celula - description: "Valor financeiro consolidado da transação." - - name: dt_ingest - description: "Timestamp da ingestão dos dados (UTC-3)." - name: programa_governo - description: "Código identificador do programa de governo." + description: "Somente pré-2026. Código identificador do programa de governo." - name: programa_governo_descricao - description: "Descrição detalhada do programa de governo." + description: "Somente pré-2026. Descrição detalhada do programa de governo." - name: acao_governo - description: "Código da ação orçamentária." + description: "Somente pré-2026. Código da ação orçamentária." - name: acao_governo_descricao - description: "Descrição da ação orçamentária." - - name: nc_evento - description: "Código do evento contábil." + description: "Somente pré-2026. Descrição da ação orçamentária." + - name: nc + description: "Comum (pré e pós-2026). Número identificador da Nota de Crédito (NC)." + - name: nc_transferencia + description: "Comum (pré e pós-2026). Número/identificador de transferência associado à NC." + - name: nc_fonte_recursos + description: "Comum (pré e pós-2026). Código da fonte de recursos (na pós-2026 vem de fonte_codigo)." + - name: nc_fonte_recursos_descricao + description: "Comum (pré e pós-2026). Descrição da fonte de recursos (na pós-2026 vem de fonte_nome)." + - name: ptres + description: "Comum (pré e pós-2026). Programa de Trabalho Resumido (PTRES)." - name: nc_evento_descricao - description: "Descrição do evento contábil." + description: "Comum (pré e pós-2026). NC - Evento Desc (na pós-2026 mapeado a partir de tipo_nc)." + - name: nc_ug_responsavel + description: "Comum (pré e pós-2026). NC - UG Responsável (na pós-2026 mapeado a partir de emitente_codigo)." + - name: nc_ug_responsavel_descricao + description: "Comum (pré e pós-2026). NC - UG Responsável Descrição (na pós-2026 mapeado a partir de emitente_nome)." - name: nc_natureza_despesa - description: "Código da natureza de despesa." + description: "Comum (pré e pós-2026). NC - Natureza Despesa (na pós-2026 mapeado a partir de gnd_codigo)." - name: nc_natureza_despesa_descricao - description: "Descrição da natureza de despesa." - - name: nc_plano_interno_descricao2 - description: "Segunda descrição detalhada do Plano Interno." + description: "Comum (pré e pós-2026). NC - Natureza Despesa Descrição (na pós-2026 mapeado a partir de gnd_nome)." + - name: nc_plano_interno + description: "Comum (pré e pós-2026). NC - Plano Interno (na pós-2026 mapeado a partir de pi_codigo)." + - name: nc_plano_interno_descricao1 + description: "Comum (pré e pós-2026). NC - Plano Interno Descrição 1 (na pós-2026 mapeado a partir de pi_nome)." + - name: favorecido_doc + description: "Comum (pré e pós-2026). Favorecido Doc (na pós-2026 mapeado a partir de favorecido_codigo)." + - name: favorecido_doc_descricao + description: "Comum (pré e pós-2026). Favorecido Doc Desc (na pós-2026 mapeado a partir de favorecido_nome)." - name: favorecido_municipio - description: "Código do município do favorecido." + description: "Somente pré-2026. Código do município do favorecido." - name: favorecido_municipio_descricao - description: "Nome do município do favorecido." + description: "Somente pré-2026. Nome do município do favorecido." + - name: valor_celula + description: "Comum (pré e pós-2026). Valor financeiro (na pré-2026 vem de nc_valor_linha; na pós-2026 vem de valor_celula)." - name: movimento_liquido_moeda_origem - description: "Valor do movimento líquido na moeda de origem." + description: "Comum (pré e pós-2026). Movimento líquido (na pré-2026 vem de movimento_liquido_moeda_origem; na pós-2026 mapeado a partir de total_lista)." + - name: dt_ingest + description: "Comum (pré e pós-2026). Timestamp da ingestão (UTC-3)." + - name: descricao + description: "Somente pós-2026. Descrição geral da NC (campo descricao)." + - name: nc_plano_interno_descricao2 + description: "Somente pré-2026. NC - Plano Interno Descrição 2." + - name: nc_evento + description: "Somente pré-2026. NC - Evento (sem match na base pós-2026)." + - name: nc_item_detalhamento + description: "Somente pós-2026. NC Item - Detalhamento (S/N) (sem match na base pré-2026)." - name: emissao_dia - description: "Dia da emissão da nota de crédito." + description: "Somente pós-2026. Dia da emissão da NC." - name: emissao_mes - description: "Mês de emissão da nota de crédito." + description: "Somente pós-2026. Mês da emissão da NC." - name: emissao_ano - description: "Ano de emissão da nota de crédito." - - name: dc - description: "Indicador de Débito ou Crédito (D/C)." - - name: emitente_codigo - description: "Código da Unidade Gestora emitente." - - name: emitente_nome - description: "Nome da Unidade Gestora emitente." - - name: gnd_codigo - description: "Código do Grupo de Natureza de Despesa." - - name: gnd_nome - description: "Nome do Grupo de Natureza de Despesa." - - name: descricao - description: "Descrição geral do documento." - - name: tipo_nc - description: "Classificação do tipo de Nota de Crédito." - - name: nc_item_detalhamento - description: "Detalhamento dos itens contidos na NC." + description: "Somente pós-2026. Ano da emissão da NC." - name: ro - description: "Registro de Operação associado." - - name: item_total - description: "Valor total do item." + description: "Somente pós-2026. Registro de Operação associado." + - name: dc + description: "Somente pós-2026. Indicador de Débito ou Crédito (D/C)." - name: total_lista - description: "Valor total da lista de itens." + description: "Somente pós-2026. Valor total da lista de itens." - name: esfera_orcamentaria_codigo - description: "Código da esfera orçamentária." + description: "Somente pós-2026. Código da esfera orçamentária." - name: esfera_orcamentaria_nome - description: "Nome da esfera orçamentária." + description: "Somente pós-2026. Nome da esfera orçamentária." tests: - verificacao_tipagem: - nome_tabela: 'siafi.nc_tesouro_mir' - nome_coluna: 'ne_ccor_ano_emissao' - tipo_esperado: 'integer' + nome_tabela: 'siafi_dbt.nc_tesouro_mir' + nome_coluna: 'ptres' + tipo_esperado: 'text' - verificacao_tipagem: - nome_tabela: 'siafi.nc_tesouro_mir' - nome_coluna: 'despesas_empenhadas' + nome_tabela: 'siafi_dbt.nc_tesouro_mir' + nome_coluna: 'valor_celula' tipo_esperado: 'numeric' - verificacao_tipagem: - nome_tabela: 'siafi.nc_tesouro_mir' - nome_coluna: 'despesas_pagas' + nome_tabela: 'siafi_dbt.nc_tesouro_mir' + nome_coluna: 'movimento_liquido_moeda_origem' tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.nc_tesouro_mir' + nome_coluna: 'emissao_dia' + tipo_esperado: 'date' - name: pf_tesouro description: > diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/nc_unificado.sql b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/nc_unificado.sql new file mode 100644 index 00000000..7201f274 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/nc_unificado.sql @@ -0,0 +1,67 @@ +{{ config(materialized="table") }} + +with + nc_tesouro as ( + select + * + from {{ ref("nc_tesouro_mir") }} + ), + + programa_por_ptres as ( + select + ptres, + programa_governo, + programa_governo_descricao, + acao_governo, + plano_orcamentario_descricao_6 + from ( + select + ptres, + programa_governo, + programa_governo_descricao, + acao_governo, + plano_orcamentario_descricao_6, + row_number() over (partition by trim(ptres) order by ptres) as rn + from {{ ref("pf_ptres") }} + ) subquery + where rn = 1 + ) + +select + coalesce(t.programa_governo, pp.programa_governo) as programa_governo, + coalesce(t.programa_governo_descricao, pp.programa_governo_descricao) as programa_governo_descricao, + coalesce(t.acao_governo, pp.acao_governo) as acao_governo, + coalesce(t.acao_governo_descricao, pp.plano_orcamentario_descricao_6) as acao_governo_descricao, + t.nc, + t.nc_transferencia, + t.nc_fonte_recursos, + t.nc_fonte_recursos_descricao, + t.ptres, + t.nc_evento_descricao, + t.nc_ug_responsavel, + t.nc_ug_responsavel_descricao, + t.nc_natureza_despesa, + t.nc_natureza_despesa_descricao, + t.nc_plano_interno, + t.nc_plano_interno_descricao1, + t.favorecido_doc, + t.favorecido_doc_descricao, + t.favorecido_municipio, + t.favorecido_municipio_descricao, + t.valor_celula, + t.movimento_liquido_moeda_origem, + t.dt_ingest, + t.descricao, + t.nc_plano_interno_descricao2, + t.nc_evento, + t.nc_item_detalhamento, + t.emissao_dia, + t.emissao_mes, + t.emissao_ano, + t.ro, + t.dc, + t.total_lista, + t.esfera_orcamentaria_codigo, + t.esfera_orcamentaria_nome +from nc_tesouro t +left join programa_por_ptres pp on trim(pp.ptres) = trim(t.ptres) \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/schema.yml b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/schema.yml index aa1f769d..bc51f497 100644 --- a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/schema.yml +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/schema.yml @@ -128,3 +128,98 @@ models: nome_tabela: 'siafi_dbt.pf_unificado_planos_acao' nome_coluna: 'vl_total_plano_acao' tipo_esperado: 'numeric' + + - name: nc_unificado + description: > + Tabela silver que parte da nc_tesouro_mir e enriquece os campos de programa e ação + com mapeamento por PTRES (programa_por_ptres/pf_ptres). + O modelo mantém o layout principal de NC do SIAFI e aplica COALESCE para preencher + programa_governo, programa_governo_descricao, acao_governo e acao_governo_descricao + quando estiverem ausentes na base original. + meta: + tags: + - silver + columns: + - name: programa_governo + description: "Código do programa de governo (SIAFI com fallback do mapeamento por PTRES)." + - name: programa_governo_descricao + description: "Descrição do programa de governo (SIAFI com fallback do mapeamento por PTRES)." + - name: acao_governo + description: "Código da ação de governo (SIAFI com fallback do mapeamento por PTRES)." + - name: acao_governo_descricao + description: "Descrição da ação de governo (SIAFI com fallback do mapeamento por PTRES)." + - name: nc + description: "Número identificador da Nota de Crédito (NC)." + - name: nc_transferencia + description: "Número/identificador de transferência da NC." + - name: nc_fonte_recursos + description: "Código da fonte de recursos." + - name: nc_fonte_recursos_descricao + description: "Descrição da fonte de recursos." + - name: ptres + description: "Programa de Trabalho Resumido (PTRES) no SIAFI." + - name: nc_evento_descricao + description: "Descrição do evento (mapeada para Tipo NC na harmonização de layouts)." + - name: nc_ug_responsavel + description: "Código da UG responsável." + - name: nc_ug_responsavel_descricao + description: "Nome da UG responsável." + - name: nc_natureza_despesa + description: "Código da natureza de despesa." + - name: nc_natureza_despesa_descricao + description: "Descrição da natureza de despesa." + - name: nc_plano_interno + description: "Código do plano interno." + - name: nc_plano_interno_descricao1 + description: "Descrição 1 do plano interno." + - name: favorecido_doc + description: "Documento (CPF/CNPJ) do favorecido." + - name: favorecido_doc_descricao + description: "Nome/razão social do favorecido." + - name: favorecido_municipio + description: "Código do município do favorecido." + - name: favorecido_municipio_descricao + description: "Descrição do município do favorecido." + - name: valor_celula + description: "Valor financeiro da célula/linha da NC no SIAFI." + - name: movimento_liquido_moeda_origem + description: "Movimento líquido na moeda de origem." + - name: descricao + description: "Descrição geral da NC (quando disponível)." + - name: nc_plano_interno_descricao2 + description: "Descrição 2 do plano interno." + - name: nc_evento + description: "Código do evento contábil (quando disponível)." + - name: nc_item_detalhamento + description: "Detalhamento da NC (S/N), quando disponível." + - name: emissao_dia + description: "Data de emissão da Nota de Crédito (SIAFI, quando disponível)." + - name: emissao_mes + description: "Mês de emissão da Nota de Crédito (SIAFI, quando disponível)." + - name: emissao_ano + description: "Ano de emissão da Nota de Crédito (SIAFI, quando disponível)." + - name: ro + description: "Registro de operação associado." + - name: dc + description: "Indicador débito/crédito." + - name: total_lista + description: "Valor total da lista." + - name: esfera_orcamentaria_codigo + description: "Código da esfera orçamentária." + - name: esfera_orcamentaria_nome + description: "Nome da esfera orçamentária." + - name: dt_ingest + description: "Timestamp de ingestão do SIAFI (UTC-3)." + tests: + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.nc_unificado' + nome_coluna: 'valor_celula' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.nc_unificado' + nome_coluna: 'emissao_dia' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.nc_unificado' + nome_coluna: 'dt_ingest' + tipo_esperado: 'timestamp with time zone' From 12e93a728b984554364150bf8ba804ad04906ad4 Mon Sep 17 00:00:00 2001 From: Luana Date: Fri, 17 Apr 2026 14:02:18 -0300 Subject: [PATCH 269/317] =?UTF-8?q?fix:=20ajusta=20a=20dag=20de=20ingest?= =?UTF-8?q?=C3=A3o=20siconv=20para=20resolver=20o=20problema=20de=20vazame?= =?UTF-8?q?nto=20de=20conex=C3=B5es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data_ingest/siconv/sincov_ingest_dag.py | 115 ++++++++++++------ 1 file changed, 78 insertions(+), 37 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py b/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py index ddab5ae8..d7cc7cb3 100644 --- a/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py @@ -1,13 +1,15 @@ import logging from datetime import datetime, timedelta from airflow.decorators import dag, task +import psycopg2 from postgres_helpers import get_postgres_conn from cliente_postgres import ClientPostgresDB from cliente_siconv import ClienteSiconv from tabelas_siconv import TABELAS_SICONV + @dag( - schedule_interval= None, + schedule_interval=None, start_date=datetime(2024, 1, 1), catchup=False, default_args={ @@ -26,77 +28,114 @@ def baixar_siconv() -> str: return cliente.ZIP_PATH @task - def ingerir_tabela(zip_path: str, nome_tabela: str, nome_csv: str, conflict_fields: list, primary_key: list, skip_rows: int, colunas: list, truncate_before_insert: bool = False) -> None: - logging.info(f"[siconv_ingest_dag.py] Iniciando ingestão da tabela {nome_tabela}") - postgres_conn_str = get_postgres_conn("postgres_mir") + def ingerir_tabela( + zip_path: str, + nome_tabela: str, + nome_csv: str, + postgres_conn_str: str, + conflict_fields: list, + primary_key: list, + skip_rows: int, + colunas: list, + truncate_before_insert: bool = False, + ) -> None: + + logging.info(f"Iniciando ingestão da tabela {nome_tabela}") + db = ClientPostgresDB(postgres_conn_str) cliente = ClienteSiconv() - if truncate_before_insert: - logging.info(f"[siconv_ingest_dag.py] Truncando tabela siconv.{nome_tabela}...") - db.execute_non_query(f""" - DO $$ BEGIN - IF EXISTS (SELECT FROM pg_tables WHERE schemaname = 'siconv' AND tablename = '{nome_tabela}') THEN - TRUNCATE TABLE siconv.{nome_tabela}; - END IF; - END $$; - """) - gerador_registros = cliente.ler_csv(nome_csv, skip_rows, colunas_esperadas=colunas) + gerador_registros = cliente.ler_csv( + nome_csv, skip_rows, colunas_esperadas=colunas + ) lote = [] tamanho_lote = 5000 total_inserido = 0 - for registro in gerador_registros: - lote.append(registro) - - if len(lote) >= tamanho_lote: + with psycopg2.connect(postgres_conn_str) as conn: + + if truncate_before_insert: + logging.info(f"Truncando tabela siconv.{nome_tabela}...") + with conn.cursor() as cursor: + cursor.execute(f""" + DO $$ BEGIN + IF EXISTS ( + SELECT FROM pg_tables + WHERE schemaname = 'siconv' + AND tablename = '{nome_tabela}' + ) THEN + TRUNCATE TABLE siconv.{nome_tabela}; + END IF; + END $$; + """) + + for registro in gerador_registros: + lote.append(registro) + + if len(lote) >= tamanho_lote: + lote = [dict(t) for t in {tuple(d.items()) for d in lote}] + + db.insert_data( + lote, + nome_tabela, + conflict_fields=conflict_fields, + primary_key=primary_key, + schema="siconv", + conn=conn, + ) + + total_inserido += len(lote) + logging.info(f"{total_inserido} registros processados...") + lote = [] + + if lote: lote = [dict(t) for t in {tuple(d.items()) for d in lote}] + db.insert_data( lote, nome_tabela, conflict_fields=conflict_fields, primary_key=primary_key, schema="siconv", + conn=conn, ) + total_inserido += len(lote) - logging.info(f"[siconv_ingest_dag.py] {total_inserido} registros processados...") - lote = [] - - if lote: - lote = [dict(t) for t in {tuple(d.items()) for d in lote}] - db.insert_data( - lote, - nome_tabela, - conflict_fields=conflict_fields, - primary_key=primary_key, - schema="siconv", - ) - total_inserido += len(lote) + + conn.commit() if total_inserido == 0: - logging.warning(f"[siconv_ingest_dag.py] Nenhum registro processado para {nome_tabela}") + logging.warning(f"Nenhum registro processado para {nome_tabela}") else: - logging.info(f"[siconv_ingest_dag.py] Ingestão finalizada: {total_inserido} registros em {nome_tabela}") - + logging.info( + f"Ingestão finalizada: {total_inserido} registros em {nome_tabela}" + ) + @task def deletar_zip(zip_path: str) -> None: import os + if os.path.exists(zip_path): os.remove(zip_path) - logging.info(f"[siconv_ingest_dag.py] Arquivo {zip_path} deletado com sucesso") + logging.info(f"Arquivo {zip_path} deletado com sucesso") else: - logging.warning(f"[siconv_ingest_dag.py] Arquivo {zip_path} não encontrado para deletar") + logging.warning(f"Arquivo {zip_path} não encontrado") path_zip = baixar_siconv() + postgres_conn_str = get_postgres_conn("postgres_mir") + ultima_task = path_zip for tabela in TABELAS_SICONV: - task_atual = ingerir_tabela.override(task_id=f"ingerir_{tabela['nome_tabela']}")( + task_atual = ingerir_tabela.override( + task_id=f"ingerir_{tabela['nome_tabela']}" + )( zip_path=path_zip, nome_tabela=tabela["nome_tabela"], nome_csv=tabela["nome_csv"], + postgres_conn_str=postgres_conn_str, conflict_fields=tabela["conflict_fields"], primary_key=tabela["primary_key"], skip_rows=tabela["skip_rows"], @@ -106,6 +145,8 @@ def deletar_zip(zip_path: str) -> None: ultima_task >> task_atual ultima_task = task_atual + ultima_task >> deletar_zip(path_zip) + siconv_ingestao_dag() \ No newline at end of file From 7c110e594a8017a52895f5b86b484d09f9e2b250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Guimar=C3=A3es=20Borges?= <131381377+lcsgborges@users.noreply.github.com> Date: Fri, 17 Apr 2026 14:06:07 -0300 Subject: [PATCH 270/317] fix: improve poetry installation check in Makefile setup (#204) --- Makefile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index fb053dcb..4cb99545 100644 --- a/Makefile +++ b/Makefile @@ -2,9 +2,12 @@ export PYTHONPATH := $(CURDIR)/airflow_lappis export MYPYPATH := $(CURDIR):$(CURDIR)/airflow_lappis/dags:$(CURDIR)/airflow_lappis/helpers:$(CURDIR)/airflow_lappis/plugins setup: - pip install poetry==1.8.5 + @if ! command -v poetry >/dev/null 2>&1; then \ + echo "Poetry não encontrado. Instale antes com pipx install poetry==1.8.5"; \ + exit 1; \ + fi + poetry self add poetry-plugin-export || true poetry config virtualenvs.in-project false - poetry config warnings.export false poetry lock poetry install --no-root --with dev poetry export --without-hashes --format=requirements.txt > requirements.generated.txt From 2629167fb4e28988aae18e30831fb867e9bcedd0 Mon Sep 17 00:00:00 2001 From: Luana Date: Fri, 17 Apr 2026 14:25:02 -0300 Subject: [PATCH 271/317] =?UTF-8?q?fix:=20=20adiciona=20suporte=20a=20cone?= =?UTF-8?q?x=C3=A3o=20externa=20no=20ClientPostgresDB=20para=20evitar=20va?= =?UTF-8?q?zamento=20de=20conex=C3=B5es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- airflow_lappis/plugins/cliente_postgres.py | 257 ++++----------------- 1 file changed, 51 insertions(+), 206 deletions(-) diff --git a/airflow_lappis/plugins/cliente_postgres.py b/airflow_lappis/plugins/cliente_postgres.py index 41c47586..adaf2385 100755 --- a/airflow_lappis/plugins/cliente_postgres.py +++ b/airflow_lappis/plugins/cliente_postgres.py @@ -1,7 +1,7 @@ import logging from typing import Any, Dict, List, Optional, Tuple import psycopg2 -import psycopg2.extras # Added import for execute_values +import psycopg2.extras from pandas import json_normalize import pandas as pd import io @@ -15,27 +15,9 @@ class ClientPostgresDB: @staticmethod def _get_column_type(value: Any) -> str: - """ - Determine PostgreSQL column type from Python value. - - Args: - value: Python value to analyze - - Returns: - PostgreSQL column type as string - """ return ClientPostgresDB.TYPE_MAP.get(type(value), "TEXT") def _flatten_data(self, data: List[Dict[str, Any]]) -> List[Dict[str, Any]]: - """ - Flatten nested JSON data. - - Args: - data (List[Dict[str, Any]]): List of nested dictionaries. - - Returns: - List[Dict[str, Any]]: List of flat dictionaries. - """ return list( map( lambda d: { @@ -60,17 +42,10 @@ def create_table_if_not_exists( table_name: str, primary_key: Optional[List[str]] = None, schema: str = "raw", + conn=None, ) -> None: - """Create table dynamically based on data structure. - - Args: - sample_data: Sample data to determine schema - table_name: Name of table to create - primary_key: List of primary key column names - schema: Database schema name - """ - with psycopg2.connect(self.conn_str) as conn: - with conn.cursor() as cursor: + def _execute(connection): + with connection.cursor() as cursor: cursor.execute(f"CREATE SCHEMA IF NOT EXISTS {schema};") logging.info(f"[cliente_postgres.py] Schema {schema} ensured to exist") @@ -104,6 +79,13 @@ def create_table_if_not_exists( f"Failed to create table {schema}.{table_name}" ) from err + if conn is not None: + _execute(conn) + else: + with psycopg2.connect(self.conn_str) as new_conn: + _execute(new_conn) + new_conn.commit() + def insert_data( self, data: List[Dict[str, Any]], @@ -111,16 +93,8 @@ def insert_data( conflict_fields: Optional[List[str]] = None, primary_key: Optional[List[str]] = None, schema: str = "raw", + conn=None, ) -> None: - """Insert data into database table. - - Args: - data: List of dictionaries to insert - table_name: Target table name - conflict_fields: List of column names for conflict resolution - primary_key: List of primary key column names - schema: Database schema name - """ if not data: logging.warning( f"[cliente_postgres.py] No data to insert into {schema}.{table_name}" @@ -128,7 +102,7 @@ def insert_data( return self.create_table_if_not_exists( - data[0], table_name, primary_key=primary_key, schema=schema + data[0], table_name, primary_key=primary_key, schema=schema, conn=conn ) flattened_data = self._flatten_data(data) @@ -142,14 +116,12 @@ def insert_data( update_str = ", ".join([f"{col} = EXCLUDED.{col}" for col in columns]) sql += f" ON CONFLICT ({conflict_str}) DO UPDATE SET {update_str}" - with psycopg2.connect(self.conn_str) as conn: - with conn.cursor() as cursor: + def _execute(connection): + with connection.cursor() as cursor: try: psycopg2.extras.execute_values(cursor, sql, values) - conn.commit() logging.info( - f"[cliente_postgres.py] Inserted data into {schema}." - f"{table_name}" + f"[cliente_postgres.py] Inserted data into {schema}.{table_name}" ) except psycopg2.Error as err: logging.error( @@ -160,15 +132,14 @@ def insert_data( f"Failed to insert data into {schema}.{table_name}" ) from err - def execute_query(self, query: str) -> List[Tuple[Any, ...]]: - """Execute a query and return the results. + if conn is not None: + _execute(conn) + else: + with psycopg2.connect(self.conn_str) as new_conn: + _execute(new_conn) + new_conn.commit() - Args: - query: SQL query to execute - - Returns: - List of tuples containing the query results - """ + def execute_query(self, query: str) -> List[Tuple[Any, ...]]: logging.info(f"[cliente_postgres.py] Executing query: {query}") with psycopg2.connect(self.conn_str) as conn: with conn.cursor() as cursor: @@ -181,80 +152,45 @@ def execute_query(self, query: str) -> List[Tuple[Any, ...]]: return results def get_contratos_ids(self, schema: str = "compras_gov") -> List[int]: - """Extrai todos os IDs de contratos da tabela contratos.""" query = f"SELECT id FROM {schema}.contratos" - with psycopg2.connect(self.conn_str) as conn: with conn.cursor() as cursor: cursor.execute(query) - contratos_ids = [row[0] for row in cursor.fetchall()] - return contratos_ids + return [row[0] for row in cursor.fetchall()] def get_id_programas(self) -> List[int]: - """Extrai todos os IDs de programas da tabela beneficiário.""" query = "SELECT id_programa FROM transfere_gov.programas" - with psycopg2.connect(self.conn_str) as conn: with conn.cursor() as cursor: cursor.execute(query) - id_programas = [row[0] for row in cursor.fetchall()] - return id_programas + return [row[0] for row in cursor.fetchall()] def get_id_planos_acao(self) -> List[int]: - """Extrai todos os IDs de planos de ação da tabela de planos de ação.""" query = "SELECT id_plano_acao FROM transfere_gov.planos_acao" - with psycopg2.connect(self.conn_str) as conn: with conn.cursor() as cursor: cursor.execute(query) - id_planos_acao = [row[0] for row in cursor.fetchall()] - return id_planos_acao + return [row[0] for row in cursor.fetchall()] def drop_table_if_exists(self, table_name: str, schema: str = "raw") -> None: - """Remove a tabela se ela existir.""" - conn = psycopg2.connect(self.conn_str) - cursor = conn.cursor() - drop_table_query = f"DROP TABLE IF EXISTS {schema}.{table_name};" - try: - cursor.execute(drop_table_query) - conn.commit() - print(f"Tabela {schema}.{table_name} removida com sucesso.") - except Exception as e: - print(f"Erro ao remover a tabela {schema}.{table_name}: {e}") - finally: - cursor.close() - conn.close() + with psycopg2.connect(self.conn_str) as conn: + with conn.cursor() as cursor: + try: + cursor.execute(f"DROP TABLE IF EXISTS {schema}.{table_name};") + conn.commit() + print(f"Tabela {schema}.{table_name} removida com sucesso.") + except Exception as e: + print(f"Erro ao remover a tabela {schema}.{table_name}: {e}") def insert_csv_data( self, csv_data: str, table_name: str, schema: str = "raw" ) -> None: - """ - Insere dados de um CSV no banco de dados, garantindo que a tabela seja criada. - Se a tabela existir, ela será removida antes da inserção dos novos dados. - - Args: - csv_data (str): Dados do CSV como string. - table_name (str): Nome da tabela de destino. - schema (str): Nome do schema do banco (padrão: "raw"). - """ - # Converte o CSV para DataFrame df = pd.read_csv(io.StringIO(csv_data)) - - # Converte o DataFrame para lista de dicionários data = df.to_dict(orient="records") - - # Remove a tabela existente self.drop_table_if_exists(table_name, schema) - - # Insere os novos dados self.insert_data(data, table_name, primary_key=None, schema=schema) def get_programacao_financeira(self) -> List[Tuple[Any, ...]]: - """Extrai o numero_programacao e ug_emitente da tabela programacao_financeira. - - Returns: - List[Tuple[Any, ...]]: Lista de tuplas com numero_programacao e ug_emitente - """ query = ( "SELECT tx_numero_programacao, ug_emitente_programacao " "FROM transfere_gov.programacao_financeira" @@ -262,27 +198,16 @@ def get_programacao_financeira(self) -> List[Tuple[Any, ...]]: with psycopg2.connect(self.conn_str) as conn: with conn.cursor() as cursor: cursor.execute(query) - programacao_financeira = cursor.fetchall() - return programacao_financeira + return cursor.fetchall() def alter_table( self, data: Dict[str, Any], table_name: str, schema: str = "raw" ) -> None: - """ - Alter table to add columns that exist in the data but not in the table. - All new columns will be created as TEXT type. - - Args: - data: Sample data containing new columns - table_name: Name of table to alter - schema: Database schema name - """ flattened_data = self._flatten_data([data])[0] columns = list(flattened_data.keys()) with psycopg2.connect(self.conn_str) as conn: with conn.cursor() as cursor: - # Get existing columns cursor.execute( f""" SELECT column_name @@ -293,7 +218,6 @@ def alter_table( ) existing_columns = [row[0] for row in cursor.fetchall()] - # Add columns that don't exist for column in columns: if column not in existing_columns: alter_query = ( @@ -314,38 +238,22 @@ def alter_table( conn.commit() logging.info( - f"[cliente_postgres.py] Table {schema}.{table_name} " - f"altered successfully" + f"[cliente_postgres.py] Table {schema}.{table_name} altered successfully" ) def get_nota_credito(self) -> List[Tuple[Any, ...]]: - """Extrai o número da nota de crédito e o valor da tabela nota_credito. - - Returns: - List[Tuple[Any, ...]]: Lista de tuplas com número da nota de crédito e valor - """ query = ( "SELECT cd_ug_emitente_nota, cd_gestao_emitente_nota, tx_numero_nota " "FROM transfere_gov.notas_de_credito" ) - with psycopg2.connect(self.conn_str) as conn: with conn.cursor() as cursor: cursor.execute(query) - nota_credito = cursor.fetchall() - return nota_credito + return cursor.fetchall() def remove_duplicates( self, table_name: str, column_mapping: Dict[int, str], schema: str = "siafi" ) -> None: - """ - Remove duplicados de uma tabela e otimiza a tabela. - - Args: - table_name (str): Nome da tabela no banco de dados. - column_mapping (Dict[int, str]): Mapeamento das colunas. - schema (str): Schema do banco de dados (padrão: "siafi"). - """ try: columns = ", ".join(column_mapping.values()) delete_query = f""" @@ -370,17 +278,13 @@ def remove_duplicates( f"Duplicados removidos com sucesso de {schema}.{table_name}" ) - conn = psycopg2.connect(self.conn_str) - conn.autocommit = True - cursor = conn.cursor() - try: - cursor.execute(vacuum_query) - logging.info( - f"VACUUM FULL executado com sucesso em {schema}.{table_name}" - ) - finally: - cursor.close() - conn.close() + with psycopg2.connect(self.conn_str) as conn: + conn.autocommit = True + with conn.cursor() as cursor: + cursor.execute(vacuum_query) + logging.info( + f"VACUUM FULL executado com sucesso em {schema}.{table_name}" + ) except Exception as e: logging.error( @@ -389,12 +293,10 @@ def remove_duplicates( raise def get_codigo_unidade(self) -> list[dict]: - """Retorna código da unidade e ordem de grandeza da tabela.""" query = """ SELECT codigounidade, ordem_grandeza FROM pessoas.unidade_organizacional """ - with psycopg2.connect(self.conn_str) as conn: with conn.cursor() as cursor: cursor.execute(query) @@ -405,12 +307,6 @@ def get_codigo_unidade(self) -> list[dict]: ] def execute_non_query(self, query: str) -> None: - """ - Executa uma query que não retorna resultados (como DDL ou blocos DO $$). - - Args: - query (str): Comando SQL que não retorna resultados. - """ logging.info(f"[cliente_postgres.py] Executando non-query: {query}") with psycopg2.connect(self.conn_str) as conn: with conn.cursor() as cursor: @@ -425,51 +321,29 @@ def execute_non_query(self, query: str) -> None: raise RuntimeError("Erro ao executar comando SQL sem retorno") from e def get_dashboard_kpis(self) -> Dict[str, int]: - """ - Busca os KPIs do dashboard de servidores. - - Returns: - Dict[str, int]: Dicionário com os KPIs - """ query = "SELECT kpi, valor FROM pessoas.kpis_servidores" - with psycopg2.connect(self.conn_str) as conn: with conn.cursor() as cursor: cursor.execute(query) - results = cursor.fetchall() - return {row[0]: row[1] for row in results} + return {row[0]: row[1] for row in cursor.fetchall()} def get_dashboard_genero(self) -> Dict[str, float]: - """ - Busca a distribuição por gênero para o dashboard. - - Returns: - Dict[str, float]: Dicionário com percentuais por gênero - """ query = """ SELECT genero, ROUND(percentual_distribuicao * 100, 1) as percentual FROM pessoas.distribuicao_genero """ - with psycopg2.connect(self.conn_str) as conn: with conn.cursor() as cursor: cursor.execute(query) - results = cursor.fetchall() genero_data = {} - for row in results: + for row in cursor.fetchall(): genero = row[0].lower() if row[0] else "n/a" genero_data[f"{genero}_percent"] = float(row[1]) return genero_data def get_dashboard_raca_cor(self) -> List[Dict[str, Any]]: - """ - Busca a distribuição por raça/cor para o dashboard. - - Returns: - List[Dict[str, Any]]: Lista de dicionários com raça/cor e quantidade - """ query = """ SELECT COALESCE(cor_raca, 'NÃO DECLARADA') as nome_cor, @@ -477,20 +351,12 @@ def get_dashboard_raca_cor(self) -> List[Dict[str, Any]]: FROM pessoas.distribuicao_raca_cor ORDER BY quantidade_servidores DESC """ - with psycopg2.connect(self.conn_str) as conn: with conn.cursor() as cursor: cursor.execute(query) - results = cursor.fetchall() - return [{"nome_cor": row[0], "valor": row[1]} for row in results] + return [{"nome_cor": row[0], "valor": row[1]} for row in cursor.fetchall()] def get_dashboard_situacao_funcional(self) -> List[Dict[str, Any]]: - """ - Busca a distribuição por situação funcional para o dashboard. - - Returns: - List[Dict[str, Any]]: Lista de dicionários com situação e quantidade - """ query = """ SELECT situacao_funcional_original as label, @@ -498,20 +364,12 @@ def get_dashboard_situacao_funcional(self) -> List[Dict[str, Any]]: FROM pessoas.distribuicao_situacao_funcional ORDER BY quantidade_servidores DESC """ - with psycopg2.connect(self.conn_str) as conn: with conn.cursor() as cursor: cursor.execute(query) - results = cursor.fetchall() - return [{"label": row[0], "valor": row[1]} for row in results] + return [{"label": row[0], "valor": row[1]} for row in cursor.fetchall()] def get_dashboard_mapa_uf(self) -> Dict[str, Dict[str, Any]]: - """ - Busca a distribuição geográfica por UF para o dashboard (mapa). - - Returns: - Dict[str, Dict[str, Any]]: Dicionário com UF como chave e dados como valor - """ query = """ SELECT sigla_uf, @@ -521,26 +379,15 @@ def get_dashboard_mapa_uf(self) -> Dict[str, Dict[str, Any]]: FROM pessoas.distribuicao_mapa_uf ORDER BY sigla_uf """ - with psycopg2.connect(self.conn_str) as conn: with conn.cursor() as cursor: cursor.execute(query) - results = cursor.fetchall() return { row[0]: {"nome": row[1], "valor": row[2], "percentual": row[3]} - for row in results + for row in cursor.fetchall() } def get_dashboard_tabela_servidores(self, limit: int = 100) -> List[Dict[str, Any]]: - """ - Busca dados agregados de servidores para exibição em tabela do dashboard. - - Args: - limit: Número máximo de registros a retornar (padrão: 100) - - Returns: - List[Dict[str, Any]]: Lista de dicionários com dados dos servidores - """ query = """ SELECT cargo, @@ -553,11 +400,9 @@ def get_dashboard_tabela_servidores(self, limit: int = 100) -> List[Dict[str, An ORDER BY total DESC LIMIT %s """ - with psycopg2.connect(self.conn_str) as conn: with conn.cursor() as cursor: cursor.execute(query, (limit,)) - results = cursor.fetchall() return [ { "cargo": row[0], @@ -567,5 +412,5 @@ def get_dashboard_tabela_servidores(self, limit: int = 100) -> List[Dict[str, An "estado": row[4], "total": row[5], } - for row in results - ] + for row in cursor.fetchall() + ] \ No newline at end of file From 331f69ebb546ba8851950f439224728c0fa1236f Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Tue, 21 Apr 2026 19:07:43 -0300 Subject: [PATCH 272/317] feat: dag historico controle parlamentares --- .../parlamentares_controle_historico_dag.py | 466 ++++++++++++++++++ airflow_lappis/plugins/cliente_deputados.py | 64 ++- airflow_lappis/plugins/cliente_senadores.py | 47 +- 3 files changed, 571 insertions(+), 6 deletions(-) create mode 100644 airflow_lappis/dags/data_ingest/dados_abertos/parlamentares_controle_historico_dag.py diff --git a/airflow_lappis/dags/data_ingest/dados_abertos/parlamentares_controle_historico_dag.py b/airflow_lappis/dags/data_ingest/dados_abertos/parlamentares_controle_historico_dag.py new file mode 100644 index 00000000..85c0cbcd --- /dev/null +++ b/airflow_lappis/dags/data_ingest/dados_abertos/parlamentares_controle_historico_dag.py @@ -0,0 +1,466 @@ +import logging +from datetime import datetime, timedelta + +import psycopg2 +import psycopg2.extras +from airflow.decorators import dag, task + +from cliente_deputados import ClienteDeputados +from cliente_postgres import ClientPostgresDB +from cliente_senadores import ClienteSenadores +from postgres_helpers import get_postgres_conn +from schedule_loader import get_dynamic_schedule + + +CONTROLE_TABLE = "parlamentares_controle" +CONTROLE_SCHEMA = "dados_abertos" + + +@dag( + schedule_interval=get_dynamic_schedule("parlamentares_controle_historico_dag"), + start_date=datetime(2025, 1, 1), + catchup=False, + default_args={ + "owner": "Tiago", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["MIR", "dados_abertos", "parlamentares", "deputados", "senadores", "historico"], +) +def parlamentares_controle_historico_dag() -> None: + """Sincroniza parlamentares atuais e controla extração de histórico por estado.""" + + @task + def sync_atuais() -> dict[str, list[int]]: + """Task 1: Busca parlamentares atuais da Câmara e do Senado.""" + logging.info( + "[parlamentares_controle_historico_dag.py] " + "Iniciando sync de parlamentares atuais" + ) + + cliente_deputados = ClienteDeputados() + cliente_senadores = ClienteSenadores() + + deputados = cliente_deputados.get_deputados_atuais() + senadores = cliente_senadores.get_senadores_atuais() + + if deputados is None: + raise RuntimeError( + "Falha ao obter snapshot atual da Camara. " + "Execucao interrompida para evitar fechamento indevido." + ) + + if not senadores: + raise RuntimeError( + "Falha ao obter snapshot atual do Senado (vazio inesperado). " + "Execucao interrompida para evitar fechamento indevido." + ) + + deputados_ids = { + int(item["id"]) + for item in deputados + if isinstance(item, dict) and item.get("id") is not None + } + senadores_ids = { + int(item.get("IdentificacaoParlamentar", {}).get("CodigoParlamentar")) + for item in senadores + if isinstance(item, dict) + and item.get("IdentificacaoParlamentar", {}).get("CodigoParlamentar") + is not None + } + + payload = { + "camara": sorted(list(deputados_ids)), + "senado": sorted(list(senadores_ids)), + } + + logging.info( + "[parlamentares_controle_historico_dag.py] Sync concluido: " + f"camara={len(payload['camara'])}, senado={len(payload['senado'])}" + ) + return payload + + @task + def state_logic(parlamentares_atuais: dict[str, list[int]]) -> list[dict[str, str]]: + """Task 2: Mantém a tabela de controle com estados de ciclo de vida.""" + conn_str = get_postgres_conn("postgres_mir") + + create_table_sql = f""" + CREATE SCHEMA IF NOT EXISTS {CONTROLE_SCHEMA}; + CREATE TABLE IF NOT EXISTS {CONTROLE_SCHEMA}.{CONTROLE_TABLE} ( + fonte TEXT NOT NULL, + parlamentar_id BIGINT NOT NULL, + status TEXT NOT NULL, + first_seen_at TIMESTAMP NOT NULL, + last_seen_at TIMESTAMP NOT NULL, + last_historico_at TIMESTAMP NULL, + updated_at TIMESTAMP NOT NULL, + PRIMARY KEY (fonte, parlamentar_id) + ); + """ + + now = datetime.now() + + def _table_exists( + cursor: psycopg2.extensions.cursor, schema: str, table: str + ) -> bool: + cursor.execute( + """ + SELECT EXISTS ( + SELECT 1 + FROM information_schema.tables + WHERE table_schema = %s + AND table_name = %s + ) + """, + (schema, table), + ) + return bool(cursor.fetchone()[0]) + + def _fetch_historico_ids( + cursor: psycopg2.extensions.cursor, schema: str, table: str + ) -> set[int]: + if not _table_exists(cursor, schema, table): + return set() + + cursor.execute( + f""" + SELECT DISTINCT CAST(id::text AS BIGINT) + FROM {schema}.{table} + WHERE id IS NOT NULL + AND id::text ~ '^[0-9]+$' + """ + ) + return {int(row[0]) for row in cursor.fetchall()} + + def _table_has_column( + cursor: psycopg2.extensions.cursor, + schema: str, + table: str, + column: str, + ) -> bool: + cursor.execute( + """ + SELECT EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_schema = %s + AND table_name = %s + AND column_name = %s + ) + """, + (schema, table, column), + ) + return bool(cursor.fetchone()[0]) + + with psycopg2.connect(conn_str) as conn: + with conn.cursor() as cursor: + cursor.execute(create_table_sql) + + cursor.execute(f"SELECT COUNT(*) FROM {CONTROLE_SCHEMA}.{CONTROLE_TABLE}") + controle_vazio = cursor.fetchone()[0] == 0 + + if controle_vazio: + historico_camara_ids = _fetch_historico_ids( + cursor, "camara_deputados", "deputados" + ) + historico_senado_ids = _fetch_historico_ids( + cursor, "senado_federal", "senadores" + ) + + for fonte, ids_historicos in ( + ("camara", historico_camara_ids), + ("senado", historico_senado_ids), + ): + ids_atuais = set(parlamentares_atuais.get(fonte, [])) + universo = ids_historicos.union(ids_atuais) + + if not universo: + continue + + values = [ + ( + fonte, + parlamentar_id, + "ATIVO" if parlamentar_id in ids_atuais else "INATIVO", + now, + now, + now, + ) + for parlamentar_id in universo + ] + + psycopg2.extras.execute_values( + cursor, + f""" + INSERT INTO {CONTROLE_SCHEMA}.{CONTROLE_TABLE} + ( + fonte, + parlamentar_id, + status, + first_seen_at, + last_seen_at, + updated_at + ) + VALUES %s + ON CONFLICT (fonte, parlamentar_id) + DO UPDATE SET + status = EXCLUDED.status, + updated_at = EXCLUDED.updated_at + """, + values, + ) + + logging.info( + "[parlamentares_controle_historico_dag.py] " + "Bootstrap realizado " + f"para fonte={fonte}: universo={len(universo)}, " + f"atuais={len(ids_atuais)}" + ) + + for fonte in ("camara", "senado"): + ids_atuais = [int(v) for v in parlamentares_atuais.get(fonte, [])] + + if ids_atuais: + values = [ + (fonte, parlamentar_id, "ATIVO", now, now, now) + for parlamentar_id in ids_atuais + ] + psycopg2.extras.execute_values( + cursor, + f""" + INSERT INTO {CONTROLE_SCHEMA}.{CONTROLE_TABLE} + ( + fonte, + parlamentar_id, + status, + first_seen_at, + last_seen_at, + updated_at + ) + VALUES %s + ON CONFLICT (fonte, parlamentar_id) + DO UPDATE SET + status = EXCLUDED.status, + last_seen_at = EXCLUDED.last_seen_at, + updated_at = EXCLUDED.updated_at + """, + values, + ) + + cursor.execute( + f""" + UPDATE {CONTROLE_SCHEMA}.{CONTROLE_TABLE} + SET status = 'PENDENTE_FECHAMENTO', + updated_at = %s + WHERE fonte = %s + AND status = 'ATIVO' + AND last_seen_at < %s + """, + (now, fonte, now), + ) + else: + logging.warning( + "[parlamentares_controle_historico_dag.py] " + "Snapshot de atuais vazio para fonte=" + f"{fonte}. Fechamento ignorado nesta execucao." + ) + + # Se já existe histórico carregado para o parlamentar, evita recarga + # inicial desnecessária marcando last_historico_at para os sem valor. + if _table_exists( + cursor, "camara_deputados", "deputados_historico" + ) and _table_has_column( + cursor, + "camara_deputados", + "deputados_historico", + "parlamentar_id", + ): + cursor.execute( + f""" + UPDATE {CONTROLE_SCHEMA}.{CONTROLE_TABLE} c + SET last_historico_at = %s, + updated_at = %s + WHERE c.fonte = 'camara' + AND c.last_historico_at IS NULL + AND EXISTS ( + SELECT 1 + FROM camara_deputados.deputados_historico h + WHERE h.parlamentar_id::text ~ '^[0-9]+$' + AND CAST(h.parlamentar_id::text AS BIGINT) + = c.parlamentar_id + ) + """, + (now, now), + ) + + if _table_exists( + cursor, "senado_federal", "senadores_historico" + ) and _table_has_column( + cursor, + "senado_federal", + "senadores_historico", + "parlamentar_id", + ): + cursor.execute( + f""" + UPDATE {CONTROLE_SCHEMA}.{CONTROLE_TABLE} c + SET last_historico_at = %s, + updated_at = %s + WHERE c.fonte = 'senado' + AND c.last_historico_at IS NULL + AND EXISTS ( + SELECT 1 + FROM senado_federal.senadores_historico h + WHERE h.parlamentar_id::text ~ '^[0-9]+$' + AND CAST(h.parlamentar_id::text AS BIGINT) + = c.parlamentar_id + ) + """, + (now, now), + ) + + cursor.execute( + f""" + SELECT fonte, parlamentar_id, status, last_historico_at + FROM {CONTROLE_SCHEMA}.{CONTROLE_TABLE} + WHERE (status = 'ATIVO' AND ( + last_historico_at IS NULL + OR last_historico_at <= NOW() - INTERVAL '7 days' + )) + OR status = 'PENDENTE_FECHAMENTO' + ORDER BY + CASE WHEN status = 'PENDENTE_FECHAMENTO' THEN 0 ELSE 1 END, + COALESCE(last_historico_at, TIMESTAMP '1900-01-01') ASC, + parlamentar_id ASC + """ + ) + rows = cursor.fetchall() + + candidatos = [ + { + "fonte": row[0], + "parlamentar_id": str(row[1]), + "status": row[2], + "last_historico_at": row[3].isoformat() if row[3] else "", + } + for row in rows + ] + + logging.info( + "[parlamentares_controle_historico_dag.py] State logic concluido. " + f"Parlamentares elegiveis para historico: {len(candidatos)}" + ) + return candidatos + + @task + def extrair_historico(candidatos: list[dict[str, str]]) -> None: + """Task 3: Extrai histórico conforme estado e atualiza ciclo de vida.""" + if not candidatos: + logging.info( + "[parlamentares_controle_historico_dag.py] " + "Nenhum parlamentar elegivel para historico" + ) + return + + conn_str = get_postgres_conn("postgres_mir") + db = ClientPostgresDB(conn_str) + cliente_deputados = ClienteDeputados() + cliente_senadores = ClienteSenadores() + + now = datetime.now() + historico_camara: list[dict] = [] + historico_senado: list[dict] = [] + status_updates: list[tuple[str, str, int]] = [] + + for candidato in candidatos: + fonte = candidato["fonte"] + parlamentar_id = int(candidato["parlamentar_id"]) + status = candidato["status"] + + try: + extracao_ok = False + + if fonte == "camara": + dados = cliente_deputados.get_historico_deputado(parlamentar_id) + if dados is not None: + extracao_ok = True + for item in dados: + if isinstance(item, dict): + item["parlamentar_id"] = parlamentar_id + item["fonte"] = fonte + item["dt_ingest"] = now.isoformat() + historico_camara.append(item) + else: + dados = cliente_senadores.get_filiacoes_senador(parlamentar_id) + if dados is not None: + extracao_ok = True + for item in dados: + if isinstance(item, dict): + item["parlamentar_id"] = parlamentar_id + item["fonte"] = fonte + item["dt_ingest"] = now.isoformat() + historico_senado.append(item) + + if not extracao_ok: + logging.warning( + "[parlamentares_controle_historico_dag.py] Sem confirmação de " + f"extração para fonte={fonte}, parlamentar_id={parlamentar_id}. " + "Status mantido." + ) + continue + + novo_status = "INATIVO" if status == "PENDENTE_FECHAMENTO" else "ATIVO" + status_updates.append((novo_status, fonte, parlamentar_id)) + except Exception as e: + logging.error( + "[parlamentares_controle_historico_dag.py] Erro ao extrair historico " + f"fonte={fonte}, parlamentar_id={parlamentar_id}: {e}" + ) + + if historico_camara: + db.insert_data( + historico_camara, + table_name="deputados_historico", + schema="camara_deputados", + ) + + if historico_senado: + db.insert_data( + historico_senado, + table_name="senadores_historico", + schema="senado_federal", + ) + + if status_updates: + with psycopg2.connect(conn_str) as conn: + with conn.cursor() as cursor: + psycopg2.extras.execute_batch( + cursor, + f""" + UPDATE {CONTROLE_SCHEMA}.{CONTROLE_TABLE} + SET status = %s, + last_historico_at = %s, + updated_at = %s + WHERE fonte = %s + AND parlamentar_id = %s + """, + [ + (status, now, now, fonte, parlamentar_id) + for status, fonte, parlamentar_id in status_updates + ], + ) + + logging.info( + "[parlamentares_controle_historico_dag.py] Extração concluida. " + f"Historico camara={len(historico_camara)}, " + f"historico senado={len(historico_senado)}, " + f"status atualizados={len(status_updates)}" + ) + + parlamentares_atuais = sync_atuais() + candidatos = state_logic(parlamentares_atuais) + extrair_historico(candidatos) + + +parlamentares_controle_historico_dag() diff --git a/airflow_lappis/plugins/cliente_deputados.py b/airflow_lappis/plugins/cliente_deputados.py index 2a3e3a03..48fa27b0 100644 --- a/airflow_lappis/plugins/cliente_deputados.py +++ b/airflow_lappis/plugins/cliente_deputados.py @@ -11,6 +11,7 @@ class ClienteDeputados(ClienteBase): BASE_URL = "https://dadosabertos.camara.leg.br/api/v2" BASE_HEADER = {"accept": "application/json"} + PAGE_SIZE = 100 def __init__(self) -> None: super().__init__(base_url=ClienteDeputados.BASE_URL) @@ -50,7 +51,11 @@ def get_all_deputados(self) -> list: pagina = 1 while True: - params = {"pagina": pagina, "itens": 1000, "dataInicio": "1823-01-01"} + params = { + "pagina": pagina, + "itens": self.PAGE_SIZE, + "dataInicio": "1823-01-01", + } deputados = self.get_deputados(**params) if not deputados: @@ -58,9 +63,64 @@ def get_all_deputados(self) -> list: all_deputados.extend(deputados) - if len(deputados) < 100: + if len(deputados) < self.PAGE_SIZE: break pagina += 1 return all_deputados + + def get_deputados_atuais(self) -> list[dict[str, Any]] | None: + """Retorna a lista atual de deputados (sem recorte histórico).""" + all_deputados = [] + pagina = 1 + + while True: + params = {"pagina": pagina, "itens": self.PAGE_SIZE} + deputados = self.get_deputados(**params) + + # Falha de API nao deve ser confundida com snapshot vazio. + if deputados is None: + logging.error( + "[cliente_deputados.py] Falha ao buscar deputados atuais na " + f"pagina={pagina}; abortando snapshot de atuais" + ) + return None + + if not deputados: + break + + all_deputados.extend(deputados) + + if len(deputados) < self.PAGE_SIZE: + break + + pagina += 1 + + return all_deputados + + def get_historico_deputado( + self, deputado_id: int | str + ) -> list[dict[str, Any]] | None: + """Obtém o histórico de um deputado específico.""" + endpoint = f"/deputados/{deputado_id}/historico" + logging.info( + f"[cliente_deputados.py] Fetching historico for deputado_id={deputado_id}" + ) + + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER + ) + + if status == http.HTTPStatus.OK and isinstance(data, dict): + historico = data.get("dados", []) + if isinstance(historico, list): + return historico + if isinstance(historico, dict): + return [historico] + + logging.warning( + "[cliente_deputados.py] Failed to fetch historico for " + f"deputado_id={deputado_id} with status: {status}" + ) + return None diff --git a/airflow_lappis/plugins/cliente_senadores.py b/airflow_lappis/plugins/cliente_senadores.py index c58f59d3..d78f01bc 100644 --- a/airflow_lappis/plugins/cliente_senadores.py +++ b/airflow_lappis/plugins/cliente_senadores.py @@ -15,13 +15,15 @@ class ClienteSenadores(ClienteBase): def __init__(self) -> None: super().__init__(base_url=ClienteSenadores.BASE_URL) logging.info( - f"[cliente_senadores.py] Initialized ClienteSenadores em: {ClienteSenadores.BASE_URL}" + "[cliente_senadores.py] Initialized ClienteSenadores em: " + f"{ClienteSenadores.BASE_URL}" ) def get_senadores_atuais(self) -> list: """ Obtém a lista de senadores em exercício. - O Senado geralmente retorna tudo em uma única chamada, sem paginação complexa como a Câmara. + O Senado geralmente retorna tudo em uma única chamada, + sem paginação complexa como a Câmara. """ endpoint = "/senador/lista/atual" logging.info("[cliente_senadores.py] Fetching senadores atuais") @@ -31,7 +33,8 @@ def get_senadores_atuais(self) -> list: ) if status == http.HTTPStatus.OK and isinstance(data, dict): - # A estrutura do JSON do Senado é: ListaParlamentarEmExercicio -> Parlamentares -> Parlamentar + # Estrutura esperada: ListaParlamentarEmExercicio -> + # Parlamentares -> Parlamentar. try: lista_root = data.get("ListaParlamentarEmExercicio", {}) parlamentares = lista_root.get("Parlamentares", {}).get("Parlamentar", []) @@ -40,7 +43,8 @@ def get_senadores_atuais(self) -> list: parlamentares = [parlamentares] logging.info( - f"[cliente_senadores.py] Successfully fetched {len(parlamentares)} senadores" + "[cliente_senadores.py] Successfully fetched " + f"{len(parlamentares)} senadores" ) return parlamentares except Exception as e: @@ -51,3 +55,38 @@ def get_senadores_atuais(self) -> list: else: logging.warning(f"[cliente_senadores.py] Failed with status: {status}") return [] + + def get_filiacoes_senador(self, senador_id: int | str) -> list[dict[str, Any]] | None: + """Obtém o histórico de filiações de um senador.""" + endpoint = f"/senador/{senador_id}/filiacoes" + logging.info( + f"[cliente_senadores.py] Fetching filiacoes for senador_id={senador_id}" + ) + + status, data = self.request( + http.HTTPMethod.GET, + endpoint, + headers=self.BASE_HEADER, + params={"v": 5}, + ) + + if status == http.HTTPStatus.OK and isinstance(data, dict): + try: + root = data.get("ListaFiliacoesParlamentar", {}) + filiacao = root.get("Filiacoes", {}).get("Filiacao", []) + + if isinstance(filiacao, dict): + return [filiacao] + if isinstance(filiacao, list): + return filiacao + except Exception as e: + logging.error( + "[cliente_senadores.py] Erro ao parsear filiacoes do senador " + f"{senador_id}: {e}" + ) + + logging.warning( + "[cliente_senadores.py] Failed to fetch filiacoes for " + f"senador_id={senador_id} with status: {status}" + ) + return None From 5269bd6798e7c072cdc579b57e7e7e068292989d Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Tue, 21 Apr 2026 21:48:18 -0300 Subject: [PATCH 273/317] feat/bolsas --- .../bolsas_pagas_ingest_dag.py | 152 ++++++++++++++++++ airflow_lappis/plugins/cliente_sqlserver.py | 13 ++ 2 files changed, 165 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/tesouro_gerencial/bolsas_pagas_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/bolsas_pagas_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/bolsas_pagas_ingest_dag.py new file mode 100644 index 00000000..cf46cc47 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/bolsas_pagas_ingest_dag.py @@ -0,0 +1,152 @@ +from typing import Dict, Any, Optional +from airflow import DAG +from airflow.operators.python import PythonOperator +from airflow.models import Variable +from datetime import datetime, timedelta +import logging +import json +import pandas as pd +import io +from schedule_loader import get_dynamic_schedule +from cliente_email import fetch_and_process_email +from cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn + +# Configuracoes basicas da DAG +default_args = { + "owner": "Davi", + "depends_on_past": False, + "retries": 1, + "retry_delay": timedelta(minutes=5), +} + +COLUMN_MAPPING = { + 0: "credor_codigo", + 1: "credor_nome", + 2: "dia_emissao", + 3: "mes_emissao", + 4: "ano_emissao", + 5: "emissao_ano", + 6: "mes_lancamento", + 7: "fonte_recursos_codigo", + 8: "fonte_recursos_descricao", + 9: "pi_codigo", + 10: "pi_descricao", + 11: "ptres", + 12: "natureza_codigo", + 13: "natureza_descricao", + 14: "processo", + 15: "valor", + 16: "observacao", + 17: "ne_ccor", + 18: "documento_habil", + 19: "item_informacao", + 20: "despesa_paga", + 21: "rp_processados", + 22: "rp_nao_processados", + 23: "pagamentos_totais", +} + +EMAIL_SUBJECT = "bolsas_pagas_ipea" +SKIPROWS = 11 + + +with DAG( + dag_id="email_bolsas_pagas_tesouro_ingest", + default_args=default_args, + description=( + "Processa anexos de bolsas pagas do Tesouro Gerencial recebidos por email " + "e insere no banco" + ), + schedule_interval=get_dynamic_schedule("bolsas_pagas_ingest_dag"), + start_date=datetime(2023, 12, 1), + catchup=False, + tags=["email", "tesouro", "bolsas_pagas"], +) as dag: + + def process_email_data(**context: Dict[str, Any]) -> Optional[Any]: + creds = json.loads(Variable.get("email_credentials")) + + email = creds["email"] + password = creds["password"] + imap_server = creds["imap_server"] + sender_email = creds["sender_email"] + + try: + logging.info("Iniciando o processamento dos emails...") + csv_data = fetch_and_process_email( + imap_server, + email, + password, + sender_email, + EMAIL_SUBJECT, + COLUMN_MAPPING, + skiprows=SKIPROWS, + ) + + if not csv_data: + logging.warning("Nenhum e-mail encontrado com o assunto esperado.") + return None + + logging.info( + "CSV processado com sucesso. Dados encontrados: %s", len(csv_data) + ) + return csv_data + except Exception as e: + logging.error("Erro no processamento dos emails: %s", str(e)) + raise + + def insert_data_to_db(**context: Dict[str, Any]) -> None: + """Insere os dados processados no banco de dados.""" + try: + task_instance: Any = context["ti"] + csv_data: Any = task_instance.xcom_pull(task_ids="process_emails") + + if not csv_data: + logging.warning("Nenhum dado para inserir no banco.") + return + + df = pd.read_csv(io.StringIO(csv_data)) + data = df.to_dict(orient="records") + + for record in data: + record["dt_ingest"] = datetime.now().isoformat() + + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + db.insert_data(data, "bolsas_pagas", schema="siafi") + + logging.info("Dados inseridos com sucesso no banco de dados.") + except Exception as e: + logging.error("Erro ao inserir dados no banco: %s", str(e)) + raise + + def clean_duplicates(**context: Dict[str, Any]) -> None: + """Remove duplicados da tabela siafi.bolsas_pagas.""" + try: + postgres_conn_str = get_postgres_conn() + db = ClientPostgresDB(postgres_conn_str) + db.remove_duplicates("bolsas_pagas", COLUMN_MAPPING, schema="siafi") + except Exception as e: + logging.error(f"Erro ao executar a limpeza de duplicados: {str(e)}") + raise + + process_emails_task = PythonOperator( + task_id="process_emails", + python_callable=process_email_data, + provide_context=True, + ) + + insert_to_db_task = PythonOperator( + task_id="insert_to_db", + python_callable=insert_data_to_db, + provide_context=True, + ) + + clean_duplicates_task = PythonOperator( + task_id="clean_duplicates", + python_callable=clean_duplicates, + provide_context=True, + ) + + process_emails_task >> insert_to_db_task >> clean_duplicates_task \ No newline at end of file diff --git a/airflow_lappis/plugins/cliente_sqlserver.py b/airflow_lappis/plugins/cliente_sqlserver.py index caf3352d..59d7d220 100644 --- a/airflow_lappis/plugins/cliente_sqlserver.py +++ b/airflow_lappis/plugins/cliente_sqlserver.py @@ -1,5 +1,6 @@ import logging import re +import math from typing import Any, Dict, List, Tuple from airflow.providers.microsoft.mssql.hooks.mssql import MsSqlHook @@ -21,6 +22,18 @@ def _sanitize_value(value: Any) -> Any: if value is None: return None + if isinstance(value, str): + cleaned_value = value.replace("\x00", "") + if cleaned_value.strip().lower() in {"nat", "nan"}: + return None + return cleaned_value + + if isinstance(value, float) and math.isnan(value): + return None + + if hasattr(value, "is_nan") and value.is_nan(): + return None + if str(value) == "NaT": return None From 529fde70a82f314bddad5f52dbca52aaedc7e04c Mon Sep 17 00:00:00 2001 From: Luana Date: Thu, 23 Apr 2026 09:49:07 -0300 Subject: [PATCH 274/317] feat: adiciona camada bronze do SICONV --- .../mir/models/siconv_dbt/bronze/convenio.sql | 50 ++ .../bronze/cronograma_desembolso.sql | 17 + .../models/siconv_dbt/bronze/desbloqueio.sql | 18 + .../models/siconv_dbt/bronze/desembolso.sql | 21 + .../mir/models/siconv_dbt/bronze/empenho.sql | 28 + .../siconv_dbt/bronze/historico_situacao.sql | 16 + .../bronze/ingresso_contrapartida.sql | 13 + .../models/siconv_dbt/bronze/licitacao.sql | 28 + .../siconv_dbt/bronze/meta_crono_fisico.sql | 27 + .../models/siconv_dbt/bronze/pagamento.sql | 29 + .../siconv_dbt/bronze/pagamento_tributo.sql | 13 + .../mir/models/siconv_dbt/bronze/proposta.sql | 46 ++ .../siconv_dbt/bronze/prorroga_oficio.sql | 17 + .../mir/models/siconv_dbt/bronze/schema.yaml | 742 ++++++++++++++++++ .../bronze/solicitacao_alteracao.sql | 20 + .../solicitacao_rendimento_aplicacao.sql | 21 + .../siconv_dbt/bronze/termo_aditivo.sql | 21 + .../dags/dbt/mir/models/sources.yml | 20 + 18 files changed, 1147 insertions(+) create mode 100644 airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/convenio.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/cronograma_desembolso.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/desbloqueio.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/desembolso.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/empenho.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/historico_situacao.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/ingresso_contrapartida.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/licitacao.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/meta_crono_fisico.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/pagamento.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/pagamento_tributo.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/proposta.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/prorroga_oficio.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/schema.yaml create mode 100644 airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/solicitacao_alteracao.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/solicitacao_rendimento_aplicacao.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/termo_aditivo.sql diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/convenio.sql b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/convenio.sql new file mode 100644 index 00000000..4fb820bf --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/convenio.sql @@ -0,0 +1,50 @@ +{{ config(materialized="table") }} + +with + convenio_raw as ( + select + nullif(nr_convenio, '')::integer as nr_convenio, + nullif(id_proposta, '')::integer as id_proposta, + nullif(dia, '')::integer as dia, + nullif(mes, '')::integer as mes, + nullif(ano, '')::integer as ano, + to_date(nullif(dia_assin_conv, ''), 'DD/MM/YYYY') as dia_assin_conv, + sit_convenio::text as sit_convenio, + subsituacao_conv::text as subsituacao_conv, + situacao_publicacao::text as situacao_publicacao, + instrumento_ativo::text as instrumento_ativo, + ind_opera_obtv::text as ind_opera_obtv, + nr_processo::text as nr_processo, + nullif(ug_emitente, '')::integer as ug_emitente, + to_date(nullif(dia_publ_conv, ''), 'DD/MM/YYYY') as dia_publ_conv, + to_date(nullif(dia_inic_vigenc_conv, ''), 'DD/MM/YYYY') as dia_inic_vigenc_conv, + to_date(nullif(dia_fim_vigenc_conv, ''), 'DD/MM/YYYY') as dia_fim_vigenc_conv, + to_date(nullif(dia_fim_vigenc_original_conv, ''), 'DD/MM/YYYY') as dia_fim_vigenc_original_conv, + nullif(dias_prest_contas, '')::integer as dias_prest_contas, + to_date(nullif(dia_limite_prest_contas, ''), 'DD/MM/YYYY') as dia_limite_prest_contas, + to_date(nullif(data_suspensiva, ''), 'DD/MM/YYYY') as data_suspensiva, + to_date(nullif(data_retirada_suspensiva, ''), 'DD/MM/YYYY') as data_retirada_suspensiva, + nullif(dias_clausula_suspensiva, '')::integer as dias_clausula_suspensiva, + situacao_contratacao::text as situacao_contratacao, + ind_assinado::text as ind_assinado, + motivo_suspensao::text as motivo_suspensao, + ind_foto::text as ind_foto, + nullif(qtde_convenios, '')::integer as qtde_convenios, + nullif(qtd_ta, '')::integer as qtd_ta, + nullif(qtd_prorroga, '')::integer as qtd_prorroga, + replace(nullif(vl_global_conv, ''), ',', '.')::numeric(15, 2) as vl_global_conv, + replace(nullif(vl_repasse_conv, ''), ',', '.')::numeric(15, 2) as vl_repasse_conv, + replace(nullif(vl_contrapartida_conv, ''), ',', '.')::numeric(15, 2) as vl_contrapartida_conv, + replace(nullif(vl_empenhado_conv, ''), ',', '.')::numeric(15, 2) as vl_empenhado_conv, + replace(nullif(vl_desembolsado_conv, ''), ',', '.')::numeric(15, 2) as vl_desembolsado_conv, + replace(nullif(vl_saldo_reman_tesouro, ''), ',', '.')::numeric(15, 2) as vl_saldo_reman_tesouro, + replace(nullif(vl_saldo_reman_convenente, ''), ',', '.')::numeric(15, 2) as vl_saldo_reman_convenente, + replace(nullif(vl_rendimento_aplicacao, ''), ',', '.')::numeric(15, 2) as vl_rendimento_aplicacao, + replace(nullif(vl_ingresso_contrapartida, ''), ',', '.')::numeric(15, 2) as vl_ingresso_contrapartida, + replace(nullif(vl_saldo_conta, ''), ',', '.')::numeric(15, 2) as vl_saldo_conta, + replace(nullif(valor_global_original_conv, ''), ',', '.')::numeric(15, 2) as valor_global_original_conv + from {{ source("siconv", "convenio") }} + ) + +select * +from convenio_raw \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/cronograma_desembolso.sql b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/cronograma_desembolso.sql new file mode 100644 index 00000000..c6ab4554 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/cronograma_desembolso.sql @@ -0,0 +1,17 @@ +{{ config(materialized="table") }} + +with + cronograma_desembolso_raw as ( + select + nullif(id_proposta, '')::integer as id_proposta, + nullif(nr_convenio, '')::integer as nr_convenio, + nullif(nr_parcela_crono_desembolso, '')::integer as nr_parcela_crono_desembolso, + nullif(mes_crono_desembolso, '')::integer as mes_crono_desembolso, + nullif(ano_crono_desembolso, '')::integer as ano_crono_desembolso, + tipo_resp_crono_desembolso::text as tipo_resp_crono_desembolso, + replace(nullif(valor_parcela_crono_desembolso, ''), ',', '.')::numeric(15, 2) as valor_parcela_crono_desembolso + from {{ source("siconv", "cronograma_desembolso") }} + ) + +select * +from cronograma_desembolso_raw \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/desbloqueio.sql b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/desbloqueio.sql new file mode 100644 index 00000000..937ed2bd --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/desbloqueio.sql @@ -0,0 +1,18 @@ +{{ config(materialized="table") }} + +with + desbloqueio_raw as ( + select + nr_convenio::integer as nr_convenio, + nr_ob::text as nr_ob, + to_date(nullif(data_cadastro, ''), 'DD/MM/YYYY') as data_cadastro, + to_date(nullif(data_envio, ''), 'DD/MM/YYYY') as data_envio, + tipo_recurso_desbloqueio::text as tipo_recurso_desbloqueio, + replace(nullif(vl_total_desbloqueio, ''), ',', '.')::numeric(15, 2) as vl_total_desbloqueio, + replace(nullif(vl_desbloqueado, ''), ',', '.')::numeric(15, 2) as vl_desbloqueado, + replace(nullif(vl_bloqueado, ''), ',', '.')::numeric(15, 2) as vl_bloqueado + from {{ source("siconv", "desbloqueio") }} + ) + +select * +from desbloqueio_raw \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/desembolso.sql b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/desembolso.sql new file mode 100644 index 00000000..58068e78 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/desembolso.sql @@ -0,0 +1,21 @@ +{{ config(materialized="table") }} + +with + desembolso_raw as ( + select + nullif(id_desembolso, '')::integer as id_desembolso, + nullif(nr_convenio, '')::integer as nr_convenio, + to_date(nullif(dt_ult_desembolso, ''), 'DD/MM/YYYY') as dt_ult_desembolso, + nullif(qtd_dias_sem_desembolso, '')::integer as qtd_dias_sem_desembolso, + to_date(nullif(data_desembolso, ''), 'DD/MM/YYYY') as data_desembolso, + nullif(ano_desembolso, '')::integer as ano_desembolso, + nullif(mes_desembolso, '')::integer as mes_desembolso, + nr_siafi::text as nr_siafi, + nullif(ug_emitente_dh, '')::integer as ug_emitente_dh, + observacao_dh::text as observacao_dh, + replace(nullif(vl_desembolsado, ''), ',', '.')::numeric(15, 2) as vl_desembolsado + from {{ source("siconv", "desembolso") }} + ) + +select * +from desembolso_raw \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/empenho.sql b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/empenho.sql new file mode 100644 index 00000000..4006f84a --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/empenho.sql @@ -0,0 +1,28 @@ +{{ config(materialized="table") }} + +with + empenho_raw as ( + select + nullif(id_empenho, '')::integer as id_empenho, + nullif(nr_convenio, '')::integer as nr_convenio, + nr_empenho::text as nr_empenho, + tipo_nota::text as tipo_nota, + desc_tipo_nota::text as desc_tipo_nota, + to_date(nullif(data_emissao, ''), 'DD/MM/YYYY') as data_emissao, + cod_situacao_empenho::text as cod_situacao_empenho, + desc_situacao_empenho::text as desc_situacao_empenho, + nullif(ug_emitente, '')::integer as ug_emitente, + nullif(ug_responsavel, '')::integer as ug_responsavel, + fonte_recurso::text as fonte_recurso, + natureza_despesa::text as natureza_despesa, + plano_interno::text as plano_interno, + ptres::text as ptres, + replace(nullif(valor_empenho, ''), ',', '.')::numeric(15, 2) as valor_empenho, + resultado_primario::text as resultado_primario, + observacao_empenho::text as observacao_empenho, + descricao_emenda_siafi::text as descricao_emenda_siafi + from {{ source("siconv", "empenho") }} + ) + +select * +from empenho_raw \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/historico_situacao.sql b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/historico_situacao.sql new file mode 100644 index 00000000..4a6f9a1f --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/historico_situacao.sql @@ -0,0 +1,16 @@ +{{ config(materialized="table") }} + +with + historico_situacao_raw as ( + select + nullif(id_proposta, '')::integer as id_proposta, + nullif(nr_convenio, '')::integer as nr_convenio, + to_date(nullif(dia_historico_sit, ''), 'DD/MM/YYYY') as dia_historico_sit, + historico_sit::text as historico_sit, + nullif(dias_historico_sit, '')::integer as dias_historico_sit, + nullif(cod_historico_sit, '')::integer as cod_historico_sit + from {{ source("siconv", "historico_situacao") }} + ) + +select * +from historico_situacao_raw \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/ingresso_contrapartida.sql b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/ingresso_contrapartida.sql new file mode 100644 index 00000000..a0eb77db --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/ingresso_contrapartida.sql @@ -0,0 +1,13 @@ +{{ config(materialized="table") }} + +with + ingresso_contrapartida_raw as ( + select + nr_convenio::integer as nr_convenio, + to_date(nullif(dt_ingresso_contrapartida, ''), 'DD/MM/YYYY') as dt_ingresso_contrapartida, + replace(nullif(vl_ingresso_contrapartida, ''), ',', '.')::numeric(15, 2) as vl_ingresso_contrapartida + from {{ source("siconv", "ingresso_contrapartida") }} + ) + +select * +from ingresso_contrapartida_raw \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/licitacao.sql b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/licitacao.sql new file mode 100644 index 00000000..95e53044 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/licitacao.sql @@ -0,0 +1,28 @@ +{{ config(materialized="table") }} + +with + licitacao_raw as ( + select + nullif(id_licitacao, '')::integer as id_licitacao, + nullif(nr_convenio, '')::integer as nr_convenio, + nr_licitacao::text as nr_licitacao, + modalidade_licitacao::text as modalidade_licitacao, + tp_processo_compra::text as tp_processo_compra, + tipo_licitacao::text as tipo_licitacao, + nr_processo_licitacao::text as nr_processo_licitacao, + to_date(nullif(data_publicacao_licitacao, ''), 'DD/MM/YYYY') as data_publicacao_licitacao, + to_date(nullif(data_abertura_licitacao, ''), 'DD/MM/YYYY') as data_abertura_licitacao, + to_date(nullif(data_encerramento_licitacao, ''), 'DD/MM/YYYY') as data_encerramento_licitacao, + to_date(nullif(data_homologacao_licitacao, ''), 'DD/MM/YYYY') as data_homologacao_licitacao, + status_licitacao::text as status_licitacao, + situacao_aceite_processo_execu::text as situacao_aceite_processo_execu, + sistema_origem::text as sistema_origem, + situacao_sistema::text as situacao_sistema, + replace(nullif(valor_licitacao, ''), ',', '.')::numeric(20, 2) as valor_licitacao, + to_date(nullif(data_analise_aceite, ''), 'DD/MM/YYYY') as data_analise_aceite, + to_date(nullif(data_envio_analise, ''), 'DD/MM/YYYY') as data_envio_analise + from {{ source("siconv", "licitacao") }} + ) + +select * +from licitacao_raw \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/meta_crono_fisico.sql b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/meta_crono_fisico.sql new file mode 100644 index 00000000..80b68611 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/meta_crono_fisico.sql @@ -0,0 +1,27 @@ +{{ config(materialized="table") }} + +with + meta_crono_fisico_raw as ( + select + nullif(id_meta, '')::integer as id_meta, + nullif(id_proposta, '')::integer as id_proposta, + nullif(nr_convenio, '')::integer as nr_convenio, + cod_programa::text as cod_programa, + nome_programa::text as nome_programa, + nullif(nr_meta, '')::integer as nr_meta, + tipo_meta::text as tipo_meta, + desc_meta::text as desc_meta, + to_date(nullif(data_inicio_meta, ''), 'DD/MM/YYYY') as data_inicio_meta, + to_date(nullif(data_fim_meta, ''), 'DD/MM/YYYY') as data_fim_meta, + uf_meta::text as uf_meta, + municipio_meta::text as municipio_meta, + endereco_meta::text as endereco_meta, + cep_meta::text as cep_meta, + replace(nullif(qtd_meta, ''), ',', '.')::numeric(15, 2) as qtd_meta, + und_fornecimento_meta::text as und_fornecimento_meta, + replace(nullif(vl_meta, ''), ',', '.')::numeric(15, 2) as vl_meta + from {{ source("siconv", "meta_crono_fisico") }} + ) + +select * +from meta_crono_fisico_raw \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/pagamento.sql b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/pagamento.sql new file mode 100644 index 00000000..6d1bc858 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/pagamento.sql @@ -0,0 +1,29 @@ +{{ config(materialized="table") }} + +with + pagamento_raw as ( + select + nullif(nr_mov_fin, '')::integer as nr_mov_fin, + nullif(nr_convenio, '')::integer as nr_convenio, + identif_fornecedor::text as identif_fornecedor, + nome_fornecedor::text as nome_fornecedor, + tp_mov_financeira::text as tp_mov_financeira, + case + when nullif(data_pag, '') is null then null + when data_pag ~ '^\d{2}/\d{2}/\d{4}$' then to_date(data_pag, 'DD/MM/YYYY') + else to_date(data_pag, 'YYYY-MM-DD') + end as data_pag, + nr_dl::text as nr_dl, + desc_dl::text as desc_dl, + replace(nullif(vl_pago, ''), ',', '.')::numeric(15, 2) as vl_pago, + nullif(id_dl, '')::integer as id_dl, + case + when nullif(data_emissao_dl, '') is null then null + when data_emissao_dl ~ '^\d{2}/\d{2}/\d{4}$' then to_date(data_emissao_dl, 'DD/MM/YYYY') + else to_date(data_emissao_dl, 'YYYY-MM-DD') + end as data_emissao_dl + from {{ source("siconv", "pagamento") }} + ) + +select * +from pagamento_raw \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/pagamento_tributo.sql b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/pagamento_tributo.sql new file mode 100644 index 00000000..9ed96673 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/pagamento_tributo.sql @@ -0,0 +1,13 @@ +{{ config(materialized="table") }} + +with + pagamento_tributo_raw as ( + select + nr_convenio::integer as nr_convenio, + to_date(nullif(data_tributo, ''), 'DD/MM/YYYY') as data_tributo, + replace(nullif(vl_pag_tributos, ''), ',', '.')::numeric(15, 2) as vl_pag_tributos + from {{ source("siconv", "pagamento_tributo") }} + ) + +select * +from pagamento_tributo_raw \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/proposta.sql b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/proposta.sql new file mode 100644 index 00000000..36ada3b4 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/proposta.sql @@ -0,0 +1,46 @@ +{{ config(materialized="table") }} + +with + proposta_raw as ( + select + id_proposta::integer as id_proposta, + uf_proponente::text as uf_proponente, + munic_proponente::text as munic_proponente, + cod_munic_ibge::integer as cod_munic_ibge, + cod_orgao_sup::integer as cod_orgao_sup, + desc_orgao_sup::text as desc_orgao_sup, + natureza_juridica::text as natureza_juridica, + nr_proposta::text as nr_proposta, + dia_prop::integer as dia_prop, + mes_prop::integer as mes_prop, + ano_prop::integer as ano_prop, + to_date(nullif(dia_proposta, ''), 'DD/MM/YYYY') as dia_proposta, + cod_orgao::integer as cod_orgao, + desc_orgao::text as desc_orgao, + modalidade::text as modalidade, + identif_proponente::text as identif_proponente, + nm_proponente::text as nm_proponente, + cep_proponente::text as cep_proponente, + endereco_proponente::text as endereco_proponente, + bairro_proponente::text as bairro_proponente, + nm_banco::text as nm_banco, + situacao_conta::text as situacao_conta, + situacao_projeto_basico::text as situacao_projeto_basico, + sit_proposta::text as sit_proposta, + to_date(nullif(dia_inic_vigencia_proposta, ''), 'DD/MM/YYYY') as dia_inic_vigencia_proposta, + to_date(nullif(dia_fim_vigencia_proposta, ''), 'DD/MM/YYYY') as dia_fim_vigencia_proposta, + objeto_proposta::text as objeto_proposta, + item_investimento::text as item_investimento, + enviada_mandataria::text as enviada_mandataria, + nome_subtipo_proposta::text as nome_subtipo_proposta, + descricao_subtipo_proposta::text as descricao_subtipo_proposta, + replace(nullif(vl_global_prop, ''), ',', '.')::numeric(15, 2) as vl_global_prop, + replace(nullif(vl_repasse_prop, ''), ',', '.')::numeric(15, 2) as vl_repasse_prop, + replace(nullif(vl_contrapartida_prop, ''), ',', '.')::numeric(15, 2) as vl_contrapartida_prop, + cd_agencia::text as cd_agencia, + cd_conta::text as cd_conta + from {{ source("siconv", "proposta") }} + ) + +select * +from proposta_raw \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/prorroga_oficio.sql b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/prorroga_oficio.sql new file mode 100644 index 00000000..d1ea2563 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/prorroga_oficio.sql @@ -0,0 +1,17 @@ +{{ config(materialized="table") }} + +with + prorroga_oficio_raw as ( + select + nullif(nr_convenio, '')::integer as nr_convenio, + nr_prorroga::text as nr_prorroga, + to_date(nullif(dt_inicio_prorroga, ''), 'DD/MM/YYYY') as dt_inicio_prorroga, + to_date(nullif(dt_fim_prorroga, ''), 'DD/MM/YYYY') as dt_fim_prorroga, + nullif(dias_prorroga, '')::integer as dias_prorroga, + to_date(nullif(dt_assinatura_prorroga, ''), 'DD/MM/YYYY') as dt_assinatura_prorroga, + sit_prorroga::text as sit_prorroga + from {{ source("siconv", "prorroga_oficio") }} + ) + +select * +from prorroga_oficio_raw \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/schema.yaml b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/schema.yaml new file mode 100644 index 00000000..fda5b622 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/schema.yaml @@ -0,0 +1,742 @@ +version: 2 + +models: + - name: proposta + description: > + Tabela da camada bronze contendo informações sobre propostas de convênios do SICONV, + com conversão de tipos, padronização de datas e valores financeiros. + meta: + tags: + - bronze + columns: + - name: id_proposta + description: "Identificador único da proposta." + - name: uf_proponente + description: "Unidade Federativa do proponente." + - name: munic_proponente + description: "Município do proponente." + - name: cod_munic_ibge + description: "Código IBGE do município." + - name: cod_orgao_sup + description: "Código do órgão superior." + - name: desc_orgao_sup + description: "Descrição do órgão superior." + - name: natureza_juridica + description: "Natureza jurídica do proponente." + - name: nr_proposta + description: "Número da proposta." + - name: dia_prop + description: "Dia da proposta." + - name: mes_prop + description: "Mês da proposta." + - name: ano_prop + description: "Ano da proposta." + - name: dia_proposta + description: "Data da proposta." + - name: cod_orgao + description: "Código do órgão." + - name: desc_orgao + description: "Descrição do órgão." + - name: modalidade + description: "Modalidade da proposta." + - name: identif_proponente + description: "Identificação do proponente (CNPJ/CPF)." + - name: nm_proponente + description: "Nome do proponente." + - name: cep_proponente + description: "CEP do proponente." + - name: endereco_proponente + description: "Endereço do proponente." + - name: bairro_proponente + description: "Bairro do proponente." + - name: nm_banco + description: "Nome do banco." + - name: situacao_conta + description: "Situação da conta bancária." + - name: situacao_projeto_basico + description: "Situação do projeto básico." + - name: sit_proposta + description: "Situação da proposta." + - name: dia_inic_vigencia_proposta + description: "Data de início da vigência da proposta." + - name: dia_fim_vigencia_proposta + description: "Data de fim da vigência da proposta." + - name: objeto_proposta + description: "Objeto da proposta." + - name: item_investimento + description: "Item de investimento." + - name: enviada_mandataria + description: "Indica se foi enviada à mandatária." + - name: nome_subtipo_proposta + description: "Nome do subtipo da proposta." + - name: descricao_subtipo_proposta + description: "Descrição do subtipo da proposta." + - name: vl_global_prop + description: "Valor global da proposta." + - name: vl_repasse_prop + description: "Valor de repasse da proposta." + - name: vl_contrapartida_prop + description: "Valor de contrapartida da proposta." + - name: cd_agencia + description: "Código da agência bancária." + - name: cd_conta + description: "Código da conta bancária." + tests: + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.proposta' + nome_coluna: 'id_proposta' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.proposta' + nome_coluna: 'vl_global_prop' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.proposta' + nome_coluna: 'dia_proposta' + tipo_esperado: 'date' + + - name: convenio + description: > + Tabela da camada bronze contendo informações sobre convênios do SICONV, + com conversão de tipos, padronização de datas e valores financeiros. + meta: + tags: + - bronze + columns: + - name: nr_convenio + description: "Número do convênio." + - name: id_proposta + description: "Identificador da proposta associada." + - name: dia + description: "Dia de assinatura do convênio." + - name: mes + description: "Mês de assinatura do convênio." + - name: ano + description: "Ano de assinatura do convênio." + - name: dia_assin_conv + description: "Data de assinatura do convênio." + - name: sit_convenio + description: "Situação do convênio." + - name: subsituacao_conv + description: "Subsituação do convênio." + - name: situacao_publicacao + description: "Situação de publicação do convênio." + - name: instrumento_ativo + description: "Indica se o instrumento está ativo." + - name: ind_opera_obtv + description: "Indicador de operação OBTV." + - name: nr_processo + description: "Número do processo." + - name: ug_emitente + description: "Código da UG emitente." + - name: dia_publ_conv + description: "Data de publicação do convênio." + - name: dia_inic_vigenc_conv + description: "Data de início da vigência do convênio." + - name: dia_fim_vigenc_conv + description: "Data de fim da vigência do convênio." + - name: dia_fim_vigenc_original_conv + description: "Data de fim da vigência original do convênio." + - name: dias_prest_contas + description: "Prazo em dias para prestação de contas." + - name: dia_limite_prest_contas + description: "Data limite para prestação de contas." + - name: data_suspensiva + description: "Data da cláusula suspensiva." + - name: data_retirada_suspensiva + description: "Data de retirada da cláusula suspensiva." + - name: dias_clausula_suspensiva + description: "Dias de cláusula suspensiva." + - name: situacao_contratacao + description: "Situação da contratação." + - name: ind_assinado + description: "Indica se o convênio foi assinado." + - name: motivo_suspensao + description: "Motivo da suspensão do convênio." + - name: ind_foto + description: "Indica se há foto associada." + - name: qtde_convenios + description: "Quantidade de convênios." + - name: qtd_ta + description: "Quantidade de termos aditivos." + - name: qtd_prorroga + description: "Quantidade de prorrogações." + - name: vl_global_conv + description: "Valor global do convênio." + - name: vl_repasse_conv + description: "Valor de repasse do convênio." + - name: vl_contrapartida_conv + description: "Valor de contrapartida do convênio." + - name: vl_empenhado_conv + description: "Valor empenhado do convênio." + - name: vl_desembolsado_conv + description: "Valor desembolsado do convênio." + - name: vl_saldo_reman_tesouro + description: "Valor do saldo remanescente do tesouro." + - name: vl_saldo_reman_convenente + description: "Valor do saldo remanescente do convenente." + - name: vl_rendimento_aplicacao + description: "Valor do rendimento de aplicação." + - name: vl_ingresso_contrapartida + description: "Valor de ingresso de contrapartida." + - name: vl_saldo_conta + description: "Valor do saldo em conta." + - name: valor_global_original_conv + description: "Valor global original do convênio." + tests: + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.convenio' + nome_coluna: 'nr_convenio' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.convenio' + nome_coluna: 'vl_global_conv' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.convenio' + nome_coluna: 'dia_assin_conv' + tipo_esperado: 'date' + + - name: desembolso + description: > + Tabela da camada bronze contendo informações sobre desembolsos de convênios do SICONV. + meta: + tags: + - bronze + columns: + - name: id_desembolso + description: "Identificador único do desembolso." + - name: nr_convenio + description: "Número do convênio associado." + - name: dt_ult_desembolso + description: "Data do último desembolso." + - name: qtd_dias_sem_desembolso + description: "Quantidade de dias sem desembolso." + - name: data_desembolso + description: "Data do desembolso." + - name: ano_desembolso + description: "Ano do desembolso." + - name: mes_desembolso + description: "Mês do desembolso." + - name: nr_siafi + description: "Número SIAFI do desembolso." + - name: ug_emitente_dh + description: "Código da UG emitente do documento hábil." + - name: observacao_dh + description: "Observação do documento hábil." + - name: vl_desembolsado + description: "Valor desembolsado." + tests: + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.desembolso' + nome_coluna: 'id_desembolso' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.desembolso' + nome_coluna: 'vl_desembolsado' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.desembolso' + nome_coluna: 'data_desembolso' + tipo_esperado: 'date' + + - name: desbloqueio + description: > + Tabela da camada bronze contendo informações sobre desbloqueios de recursos de convênios do SICONV. + meta: + tags: + - bronze + columns: + - name: nr_convenio + description: "Número do convênio associado." + - name: nr_ob + description: "Número da ordem bancária." + - name: data_cadastro + description: "Data de cadastro do desbloqueio." + - name: data_envio + description: "Data de envio do desbloqueio." + - name: tipo_recurso_desbloqueio + description: "Tipo de recurso desbloqueado." + - name: vl_total_desbloqueio + description: "Valor total do desbloqueio." + - name: vl_desbloqueado + description: "Valor desbloqueado." + - name: vl_bloqueado + description: "Valor bloqueado." + tests: + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.desbloqueio' + nome_coluna: 'nr_convenio' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.desbloqueio' + nome_coluna: 'vl_total_desbloqueio' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.desbloqueio' + nome_coluna: 'data_cadastro' + tipo_esperado: 'date' + + - name: solicitacao_alteracao + description: > + Tabela da camada bronze contendo informações sobre solicitações de alteração de convênios do SICONV. + meta: + tags: + - bronze + columns: + - name: id_solicitacao + description: "Identificador único da solicitação." + - name: nr_convenio + description: "Número do convênio associado." + - name: nr_solicitacao + description: "Número da solicitação." + - name: situacao_solicitacao + description: "Situação da solicitação." + - name: objeto_solicitacao + description: "Objeto da solicitação." + - name: data_solicitacao + description: "Data da solicitação." + tests: + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.solicitacao_alteracao' + nome_coluna: 'id_solicitacao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.solicitacao_alteracao' + nome_coluna: 'data_solicitacao' + tipo_esperado: 'date' + + - name: termo_aditivo + description: > + Tabela da camada bronze contendo informações sobre termos aditivos de convênios do SICONV. + meta: + tags: + - bronze + columns: + - name: nr_convenio + description: "Número do convênio associado." + - name: id_solicitacao + description: "Identificador da solicitação associada." + - name: numero_ta + description: "Número do termo aditivo." + - name: tipo_ta + description: "Tipo do termo aditivo." + - name: vl_global_ta + description: "Valor global do termo aditivo." + - name: vl_repasse_ta + description: "Valor de repasse do termo aditivo." + - name: vl_contrapartida_ta + description: "Valor de contrapartida do termo aditivo." + - name: dt_assinatura_ta + description: "Data de assinatura do termo aditivo." + - name: dt_inicio_ta + description: "Data de início do termo aditivo." + - name: dt_fim_ta + description: "Data de fim do termo aditivo." + - name: justificativa_ta + description: "Justificativa do termo aditivo." + tests: + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.termo_aditivo' + nome_coluna: 'nr_convenio' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.termo_aditivo' + nome_coluna: 'vl_global_ta' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.termo_aditivo' + nome_coluna: 'dt_assinatura_ta' + tipo_esperado: 'date' + + - name: solicitacao_rendimento_aplicacao + description: > + Tabela da camada bronze contendo informações sobre solicitações de rendimento de aplicação do SICONV. + meta: + tags: + - bronze + columns: + - name: id_solicitacao_rend_aplicacao + description: "Identificador único da solicitação de rendimento." + - name: nr_convenio + description: "Número do convênio associado." + - name: nr_solicitacao_rend_aplicacao + description: "Número da solicitação de rendimento." + - name: status_solicitacao_rend_aplicacao + description: "Status da solicitação de rendimento." + - name: data_solicitacao_rend_aplicacao + description: "Data da solicitação de rendimento." + - name: valor_solicitacao_rend_aplicacao + description: "Valor solicitado de rendimento." + - name: valor_aprovado_solicitacao_rend_aplicacao + description: "Valor aprovado de rendimento." + tests: + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.solicitacao_rendimento_aplicacao' + nome_coluna: 'id_solicitacao_rend_aplicacao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.solicitacao_rendimento_aplicacao' + nome_coluna: 'valor_solicitacao_rend_aplicacao' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.solicitacao_rendimento_aplicacao' + nome_coluna: 'data_solicitacao_rend_aplicacao' + tipo_esperado: 'date' + + - name: prorroga_oficio + description: > + Tabela da camada bronze contendo informações sobre prorrogações de ofício de convênios do SICONV. + meta: + tags: + - bronze + columns: + - name: nr_convenio + description: "Número do convênio associado." + - name: nr_prorroga + description: "Número da prorrogação." + - name: dt_inicio_prorroga + description: "Data de início da prorrogação." + - name: dt_fim_prorroga + description: "Data de fim da prorrogação." + - name: dias_prorroga + description: "Quantidade de dias de prorrogação." + - name: dt_assinatura_prorroga + description: "Data de assinatura da prorrogação." + - name: sit_prorroga + description: "Situação da prorrogação." + tests: + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.prorroga_oficio' + nome_coluna: 'nr_convenio' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.prorroga_oficio' + nome_coluna: 'dt_inicio_prorroga' + tipo_esperado: 'date' + + - name: pagamento_tributo + description: > + Tabela da camada bronze contendo informações sobre pagamentos de tributos de convênios do SICONV. + meta: + tags: + - bronze + columns: + - name: nr_convenio + description: "Número do convênio associado." + - name: data_tributo + description: "Data do pagamento do tributo." + - name: vl_pag_tributos + description: "Valor pago de tributos." + tests: + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.pagamento_tributo' + nome_coluna: 'nr_convenio' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.pagamento_tributo' + nome_coluna: 'vl_pag_tributos' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.pagamento_tributo' + nome_coluna: 'data_tributo' + tipo_esperado: 'date' + + - name: pagamento + description: > + Tabela da camada bronze contendo informações sobre pagamentos de convênios do SICONV. + meta: + tags: + - bronze + columns: + - name: nr_mov_fin + description: "Número do movimento financeiro." + - name: nr_convenio + description: "Número do convênio associado." + - name: identif_fornecedor + description: "Identificação do fornecedor (CNPJ/CPF)." + - name: nome_fornecedor + description: "Nome do fornecedor." + - name: tp_mov_financeira + description: "Tipo do movimento financeiro." + - name: data_pag + description: "Data do pagamento." + - name: nr_dl + description: "Número do documento de liquidação." + - name: desc_dl + description: "Descrição do documento de liquidação." + - name: vl_pago + description: "Valor pago." + - name: id_dl + description: "Identificador do documento de liquidação." + - name: data_emissao_dl + description: "Data de emissão do documento de liquidação." + tests: + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.pagamento' + nome_coluna: 'nr_mov_fin' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.pagamento' + nome_coluna: 'vl_pago' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.pagamento' + nome_coluna: 'data_pag' + tipo_esperado: 'date' + + - name: licitacao + description: > + Tabela da camada bronze contendo informações sobre licitações de convênios do SICONV. + meta: + tags: + - bronze + columns: + - name: id_licitacao + description: "Identificador único da licitação." + - name: nr_convenio + description: "Número do convênio associado." + - name: nr_licitacao + description: "Número da licitação." + - name: modalidade_licitacao + description: "Modalidade da licitação." + - name: tp_processo_compra + description: "Tipo do processo de compra." + - name: tipo_licitacao + description: "Tipo da licitação." + - name: nr_processo_licitacao + description: "Número do processo de licitação." + - name: data_publicacao_licitacao + description: "Data de publicação da licitação." + - name: data_abertura_licitacao + description: "Data de abertura da licitação." + - name: data_encerramento_licitacao + description: "Data de encerramento da licitação." + - name: data_homologacao_licitacao + description: "Data de homologação da licitação." + - name: status_licitacao + description: "Status da licitação." + - name: situacao_aceite_processo_execu + description: "Situação de aceite do processo de execução." + - name: sistema_origem + description: "Sistema de origem da licitação." + - name: situacao_sistema + description: "Situação no sistema." + - name: valor_licitacao + description: "Valor da licitação." + - name: data_analise_aceite + description: "Data de análise de aceite." + - name: data_envio_analise + description: "Data de envio para análise." + tests: + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.licitacao' + nome_coluna: 'id_licitacao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.licitacao' + nome_coluna: 'valor_licitacao' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.licitacao' + nome_coluna: 'data_publicacao_licitacao' + tipo_esperado: 'date' + + - name: ingresso_contrapartida + description: > + Tabela da camada bronze contendo informações sobre ingressos de contrapartida de convênios do SICONV. + meta: + tags: + - bronze + columns: + - name: nr_convenio + description: "Número do convênio associado." + - name: dt_ingresso_contrapartida + description: "Data de ingresso da contrapartida." + - name: vl_ingresso_contrapartida + description: "Valor de ingresso da contrapartida." + tests: + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.ingresso_contrapartida' + nome_coluna: 'nr_convenio' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.ingresso_contrapartida' + nome_coluna: 'vl_ingresso_contrapartida' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.ingresso_contrapartida' + nome_coluna: 'dt_ingresso_contrapartida' + tipo_esperado: 'date' + + - name: empenho + description: > + Tabela da camada bronze contendo informações sobre empenhos de convênios do SICONV. + meta: + tags: + - bronze + columns: + - name: id_empenho + description: "Identificador único do empenho." + - name: nr_convenio + description: "Número do convênio associado." + - name: nr_empenho + description: "Número do empenho." + - name: tipo_nota + description: "Tipo da nota de empenho." + - name: desc_tipo_nota + description: "Descrição do tipo da nota." + - name: data_emissao + description: "Data de emissão do empenho." + - name: cod_situacao_empenho + description: "Código da situação do empenho." + - name: desc_situacao_empenho + description: "Descrição da situação do empenho." + - name: ug_emitente + description: "Código da UG emitente." + - name: ug_responsavel + description: "Código da UG responsável." + - name: fonte_recurso + description: "Fonte de recurso do empenho." + - name: natureza_despesa + description: "Natureza da despesa." + - name: plano_interno + description: "Plano interno do empenho." + - name: ptres + description: "Programa de Trabalho Resumido." + - name: valor_empenho + description: "Valor do empenho." + - name: resultado_primario + description: "Resultado primário do empenho." + - name: observacao_empenho + description: "Observação do empenho." + - name: descricao_emenda_siafi + description: "Descrição da emenda SIAFI." + tests: + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.empenho' + nome_coluna: 'id_empenho' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.empenho' + nome_coluna: 'valor_empenho' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.empenho' + nome_coluna: 'data_emissao' + tipo_esperado: 'date' + + - name: historico_situacao + description: > + Tabela da camada bronze contendo o histórico de situações de convênios do SICONV. + meta: + tags: + - bronze + columns: + - name: id_proposta + description: "Identificador da proposta." + - name: nr_convenio + description: "Número do convênio associado." + - name: dia_historico_sit + description: "Data do histórico de situação." + - name: historico_sit + description: "Descrição do histórico de situação." + - name: dias_historico_sit + description: "Quantidade de dias no histórico." + - name: cod_historico_sit + description: "Código do histórico de situação." + tests: + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.historico_situacao' + nome_coluna: 'nr_convenio' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.historico_situacao' + nome_coluna: 'dia_historico_sit' + tipo_esperado: 'date' + + - name: cronograma_desembolso + description: > + Tabela da camada bronze contendo informações sobre cronogramas de desembolso de convênios do SICONV. + meta: + tags: + - bronze + columns: + - name: id_proposta + description: "Identificador da proposta." + - name: nr_convenio + description: "Número do convênio associado." + - name: nr_parcela_crono_desembolso + description: "Número da parcela do cronograma de desembolso." + - name: mes_crono_desembolso + description: "Mês do cronograma de desembolso." + - name: ano_crono_desembolso + description: "Ano do cronograma de desembolso." + - name: tipo_resp_crono_desembolso + description: "Tipo de responsável pelo cronograma de desembolso." + - name: valor_parcela_crono_desembolso + description: "Valor da parcela do cronograma de desembolso." + tests: + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.cronograma_desembolso' + nome_coluna: 'nr_convenio' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.cronograma_desembolso' + nome_coluna: 'valor_parcela_crono_desembolso' + tipo_esperado: 'numeric' + + - name: meta_crono_fisico + description: > + Tabela da camada bronze contendo informações sobre metas do cronograma físico de convênios do SICONV. + meta: + tags: + - bronze + columns: + - name: id_meta + description: "Identificador único da meta." + - name: id_proposta + description: "Identificador da proposta." + - name: nr_convenio + description: "Número do convênio associado." + - name: cod_programa + description: "Código do programa." + - name: nome_programa + description: "Nome do programa." + - name: nr_meta + description: "Número da meta." + - name: tipo_meta + description: "Tipo da meta." + - name: desc_meta + description: "Descrição da meta." + - name: data_inicio_meta + description: "Data de início da meta." + - name: data_fim_meta + description: "Data de fim da meta." + - name: uf_meta + description: "UF da meta." + - name: municipio_meta + description: "Município da meta." + - name: endereco_meta + description: "Endereço da meta." + - name: cep_meta + description: "CEP da meta." + - name: qtd_meta + description: "Quantidade da meta." + - name: und_fornecimento_meta + description: "Unidade de fornecimento da meta." + - name: vl_meta + description: "Valor da meta." + tests: + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.meta_crono_fisico' + nome_coluna: 'id_meta' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.meta_crono_fisico' + nome_coluna: 'vl_meta' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.meta_crono_fisico' + nome_coluna: 'data_inicio_meta' + tipo_esperado: 'date' \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/solicitacao_alteracao.sql b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/solicitacao_alteracao.sql new file mode 100644 index 00000000..e7d3736a --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/solicitacao_alteracao.sql @@ -0,0 +1,20 @@ +{{ config(materialized="table") }} + +with + solicitacao_alteracao_raw as ( + select + nullif(id_solicitacao, '')::integer as id_solicitacao, + nullif(nr_convenio, '')::integer as nr_convenio, + nr_solicitacao::text as nr_solicitacao, + situacao_solicitacao::text as situacao_solicitacao, + objeto_solicitacao::text as objeto_solicitacao, + case + when nullif(data_solicitacao, '') is null then null + when data_solicitacao ~ '^\d{2}/\d{2}/\d{4}$' then to_date(data_solicitacao, 'DD/MM/YYYY') + else to_date(data_solicitacao, 'YYYY-MM-DD') + end as data_solicitacao + from {{ source("siconv", "solicitacao_alteracao") }} + ) + +select * +from solicitacao_alteracao_raw \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/solicitacao_rendimento_aplicacao.sql b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/solicitacao_rendimento_aplicacao.sql new file mode 100644 index 00000000..21e14d77 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/solicitacao_rendimento_aplicacao.sql @@ -0,0 +1,21 @@ +{{ config(materialized="table") }} + +with + solicitacao_rendimento_aplicacao_raw as ( + select + nullif(id_solicitacao_rend_aplicacao, '')::integer as id_solicitacao_rend_aplicacao, + nullif(nr_convenio, '')::integer as nr_convenio, + nr_solicitacao_rend_aplicacao::text as nr_solicitacao_rend_aplicacao, + status_solicitacao_rend_aplicacao::text as status_solicitacao_rend_aplicacao, + case + when nullif(data_solicitacao_rend_aplicacao, '') is null then null + when data_solicitacao_rend_aplicacao ~ '^\d{2}/\d{2}/\d{4}$' then to_date(data_solicitacao_rend_aplicacao, 'DD/MM/YYYY') + else to_date(data_solicitacao_rend_aplicacao, 'YYYY-MM-DD') + end as data_solicitacao_rend_aplicacao, + replace(nullif(valor_solicitacao_rend_aplicacao, ''), ',', '.')::numeric(15, 2) as valor_solicitacao_rend_aplicacao, + replace(nullif(valor_aprovado_solicitacao_rend_aplicacao, ''), ',', '.')::numeric(15, 2) as valor_aprovado_solicitacao_rend_aplicacao + from {{ source("siconv", "solicitacao_rendimento_aplicacao") }} + ) + +select * +from solicitacao_rendimento_aplicacao_raw \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/termo_aditivo.sql b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/termo_aditivo.sql new file mode 100644 index 00000000..be595030 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/bronze/termo_aditivo.sql @@ -0,0 +1,21 @@ +{{ config(materialized="table") }} + +with + termo_aditivo_raw as ( + select + nullif(nr_convenio, '')::integer as nr_convenio, + nullif(id_solicitacao, '')::integer as id_solicitacao, + numero_ta::text as numero_ta, + tipo_ta::text as tipo_ta, + replace(nullif(vl_global_ta, ''), ',', '.')::numeric(15, 2) as vl_global_ta, + replace(nullif(vl_repasse_ta, ''), ',', '.')::numeric(15, 2) as vl_repasse_ta, + replace(nullif(vl_contrapartida_ta, ''), ',', '.')::numeric(15, 2) as vl_contrapartida_ta, + to_date(nullif(dt_assinatura_ta, ''), 'DD/MM/YYYY') as dt_assinatura_ta, + to_date(nullif(dt_inicio_ta, ''), 'DD/MM/YYYY') as dt_inicio_ta, + to_date(nullif(dt_fim_ta, ''), 'DD/MM/YYYY') as dt_fim_ta, + justificativa_ta::text as justificativa_ta + from {{ source("siconv", "termo_aditivo") }} + ) + +select * +from termo_aditivo_raw \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/sources.yml b/airflow_lappis/dags/dbt/mir/models/sources.yml index d344ed3d..491a0b8d 100644 --- a/airflow_lappis/dags/dbt/mir/models/sources.yml +++ b/airflow_lappis/dags/dbt/mir/models/sources.yml @@ -44,3 +44,23 @@ sources: - name: nc_tesouro_pre_2026 - name: pf_tesouro - name: programacao_acao_ptres + + - name: siconv + schema: siconv + tables: + - name: proposta + - name: convenio + - name: desembolso + - name: desbloqueio + - name: solicitacao_alteracao + - name: termo_aditivo + - name: solicitacao_rendimento_aplicacao + - name: prorroga_oficio + - name: pagamento_tributo + - name: pagamento + - name: licitacao + - name: ingresso_contrapartida + - name: empenho + - name: historico_situacao + - name: cronograma_desembolso + - name: meta_crono_fisico \ No newline at end of file From 88a146c2f20679a680f67a0f1d2617c455925536 Mon Sep 17 00:00:00 2001 From: Mateus de Castro <140627829+mat054@users.noreply.github.com> Date: Thu, 23 Apr 2026 19:48:58 -0300 Subject: [PATCH 275/317] feat: dbts bronze do ipea_pro e do sisbolsas (#228) Co-authored-by: Davi --- airflow_lappis/dags/dbt/ipea/dbt_project.yml | 5 + .../dags/dbt/ipea/macros/safe_casts.sql | 50 + .../bronze/ipea_pro_bolsistas.sql | 50 + .../bronze/ipea_pro_coordenacoes.sql | 18 + .../bronze/ipea_pro_custosemprojetos.sql | 18 + .../bronze/ipea_pro_diretorias.sql | 17 + .../bronze/ipea_pro_fontesreceitas.sql | 18 + .../bronze/ipea_pro_grupoentidade.sql | 12 + .../bronze/ipea_pro_insumofinanceiro.sql | 16 + .../bronze/ipea_pro_itemfontereceitas.sql | 14 + .../bronze/ipea_pro_itenscustos.sql | 20 + .../bronze/ipea_pro_projetos.sql | 44 + .../ipea_pro_registrofinanceiroemprojetos.sql | 18 + .../bronze/ipea_pro_servidorespublicos.sql | 57 + .../bronze/ipea_pro_statusprojetos.sql | 14 + .../sistema_sisbolsas/bronze/schema.yml | 1172 +++++++++++++++++ .../bronze/sisbolsas_tb_area_formacao.sql | 12 + .../bronze/sisbolsas_tb_bolsa.sql | 16 + .../bronze/sisbolsas_tb_bolsa_coordenador.sql | 17 + .../sisbolsas_tb_bolsa_fonterecurso.sql | 17 + .../bronze/sisbolsas_tb_bolsista.sql | 17 + .../bronze/sisbolsas_tb_chamada_publica.sql | 35 + .../bronze/sisbolsas_tb_chapubli_fontfina.sql | 12 + .../bronze/sisbolsas_tb_chapubli_unidade.sql | 12 + .../bronze/sisbolsas_tb_coordenador.sql | 12 + .../bronze/sisbolsas_tb_dado_formacao.sql | 25 + .../bronze/sisbolsas_tb_dado_pessoal.sql | 35 + .../bronze/sisbolsas_tb_dado_profissional.sql | 20 + .../bronze/sisbolsas_tb_estado.sql | 13 + .../bronze/sisbolsas_tb_folha_bolsista.sql | 22 + .../bronze/sisbolsas_tb_folha_pagamento.sql | 22 + .../bronze/sisbolsas_tb_fonte_financeira.sql | 12 + .../bronze/sisbolsas_tb_modalidade.sql | 16 + .../sisbolsas_tb_nivel_escolaridade.sql | 12 + .../bronze/sisbolsas_tb_processo_seletivo.sql | 31 + .../bronze/sisbolsas_tb_programa.sql | 13 + .../bronze/sisbolsas_tb_selecao.sql | 35 + .../bronze/sisbolsas_tb_situacao_chamada.sql | 12 + .../sisbolsas_tb_situacao_concessao.sql | 12 + .../bronze/sisbolsas_tb_unidade.sql | 15 + .../bronze/sisbolsas_tb_usuario.sql | 22 + .../dags/dbt/ipea/models/sources.yml | 46 + 42 files changed, 2056 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/macros/safe_casts.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_bolsistas.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_coordenacoes.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_custosemprojetos.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_diretorias.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_fontesreceitas.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_grupoentidade.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_insumofinanceiro.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_itemfontereceitas.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_itenscustos.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_projetos.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_registrofinanceiroemprojetos.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_servidorespublicos.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_statusprojetos.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/schema.yml create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_area_formacao.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_bolsa.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_bolsa_coordenador.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_bolsa_fonterecurso.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_bolsista.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_chamada_publica.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_chapubli_fontfina.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_chapubli_unidade.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_coordenador.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_dado_formacao.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_dado_pessoal.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_dado_profissional.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_estado.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_folha_bolsista.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_folha_pagamento.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_fonte_financeira.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_modalidade.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_nivel_escolaridade.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_processo_seletivo.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_programa.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_selecao.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_situacao_chamada.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_situacao_concessao.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_unidade.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_usuario.sql diff --git a/airflow_lappis/dags/dbt/ipea/dbt_project.yml b/airflow_lappis/dags/dbt/ipea/dbt_project.yml index 17337daa..e96aac73 100755 --- a/airflow_lappis/dags/dbt/ipea/dbt_project.yml +++ b/airflow_lappis/dags/dbt/ipea/dbt_project.yml @@ -45,6 +45,11 @@ models: +schema: orcamento views: +materialized: view + sistema_sisbolsas: + +materialized: table + +schema: sistema_sisbolsas + bronze: + +materialized: table # siape_dbt: # +materialized: table # +schema: siape diff --git a/airflow_lappis/dags/dbt/ipea/macros/safe_casts.sql b/airflow_lappis/dags/dbt/ipea/macros/safe_casts.sql new file mode 100644 index 00000000..811b6bbd --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/macros/safe_casts.sql @@ -0,0 +1,50 @@ +{% macro safe_bigint(column_name) -%} + case + when {{ column_name }} is null then null + when nullif(trim({{ column_name }}::text), '') is null then null + when upper(trim({{ column_name }}::text)) = 'NAN' then null + when trim({{ column_name }}::text) ~ '^[+-]?[0-9]+$' then trim({{ column_name }}::text)::bigint + else null + end +{%- endmacro %} + +{% macro safe_numeric(column_name, precision=18, scale=2) -%} + case + when {{ column_name }} is null then null + when nullif(trim({{ column_name }}::text), '') is null then null + when upper(trim({{ column_name }}::text)) = 'NAN' then null + when trim({{ column_name }}::text) ~ '^[+-]?([0-9]+([.][0-9]+)?|[.][0-9]+)$' + then trim({{ column_name }}::text)::numeric({{ precision }}, {{ scale }}) + else null + end +{%- endmacro %} + +{% macro safe_boolean(column_name) -%} + case + when {{ column_name }} is null then null + when nullif(trim({{ column_name }}::text), '') is null then null + when lower(trim({{ column_name }}::text)) in ('true', 't', '1', 'sim', 's') then true + when lower(trim({{ column_name }}::text)) in ('false', 'f', '0', 'nao', 'não', 'n') then false + else null + end +{%- endmacro %} + +{% macro safe_date(column_name) -%} + case + when {{ column_name }} is null then null + when trim({{ column_name }}::text) in ('', '0001-01-01', '1900-01-01') then null + when upper(trim({{ column_name }}::text)) = 'NAN' then null + when trim({{ column_name }}::text) ~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}$' + then trim({{ column_name }}::text)::date + else null + end +{%- endmacro %} + +{% macro safe_timestamp(column_name) -%} + case + when {{ column_name }} is null then null + when nullif(trim({{ column_name }}::text), '') is null then null + when upper(trim({{ column_name }}::text)) = 'NAN' then null + else {{ column_name }}::timestamp + end +{%- endmacro %} diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_bolsistas.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_bolsistas.sql new file mode 100644 index 00000000..19fe752a --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_bolsistas.sql @@ -0,0 +1,50 @@ +{{ config(materialized="table") }} + +with + bolsistas as ( + select + {{ safe_bigint('bolsistaid') }} as bolsistaid, + {{ safe_bigint('categoriaadicionalbolsistaid') }} as categoriaadicionalbolsistaid, + nomebolsista::text as nomebolsista, + {{ safe_date('datanascimento') }} as datanascimento, + regexp_replace(numerocpf, '[^0-9]', '', 'g')::text as numerocpf, + numerorg::text as numerorg, + expedidorrg::text as expedidorrg, + {{ safe_bigint('coordenadorid') }} as coordenadorid, + {{ safe_bigint('diretoriaid') }} as diretoriaid, + {{ safe_bigint('coordenacaoid') }} as coordenacaoid, + {{ safe_bigint('unidadeipeaid') }} as unidadeipeaid, + email1::text as email1, + email2::text as email2, + email3::text as email3, + telefone1::text as telefone1, + telefone2::text as telefone2, + telefone3::text as telefone3, + regexp_replace(residenciacep, '[^0-9]', '', 'g')::text as residenciacep, + residenciarua::text as residenciarua, + residencianumero::text as residencianumero, + residenciacomplemento::text as residenciacomplemento, + residenciabairro::text as residenciabairro, + bolsistaminicv::text as bolsistaminicv, + linktolattes::text as linktolattes, + {{ safe_bigint('ufid') }} as ufid, + {{ safe_bigint('municipioid') }} as municipioid, + {{ safe_date('bolsadatainicio') }} as bolsadatainicio, + {{ safe_date('bolsadatafinalizacao') }} as bolsadatafinalizacao, + {{ safe_bigint('categoriabolsistaid') }} as categoriabolsistaid, + {{ safe_bigint('projetoid') }} as projetoid, + {{ safe_bigint('sexoid') }} as sexoid, + {{ safe_bigint('situacaopessoaid') }} as situacaopessoaid, + {{ safe_bigint('statuscadastroid') }} as statuscadastroid, + {{ safe_bigint('custoemprojetoid') }} as custoemprojetoid, + login::text as login, + senha::text as senha, + {{ safe_bigint('usuarioid') }} as usuarioid, + {{ safe_bigint('idnosistemalegado') }} as idnosistemalegado, + matriculaipea::text as matriculaipea, + observacoes::text as observacoes + from {{ source("ipea_pro", "bolsistas") }} + ) + +select * +from bolsistas diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_coordenacoes.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_coordenacoes.sql new file mode 100644 index 00000000..6a3bd580 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_coordenacoes.sql @@ -0,0 +1,18 @@ +{{ config(materialized="table") }} + +with + coordenacoes as ( + select + {{ safe_bigint('coordenacaoid') }} as coordenacaoid, + coordenacaonome::text as coordenacaonome, + coordenacaosigla::text as coordenacaosigla, + coordenacaodescricao::text as coordenacaodescricao, + {{ safe_bigint('coordenadorid') }} as coordenadorid, + {{ safe_bigint('diretoriaid') }} as diretoriaid, + {{ safe_boolean('ativa') }} as ativa, + codigosiape::text as codigosiape + from {{ source("ipea_pro", "coordenacoes") }} + ) + +select * +from coordenacoes diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_custosemprojetos.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_custosemprojetos.sql new file mode 100644 index 00000000..9a77e449 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_custosemprojetos.sql @@ -0,0 +1,18 @@ +{{ config(materialized="table") }} + +with + custosemprojetos as ( + select + {{ safe_bigint('custoemprojetoid') }} as custoemprojetoid, + {{ safe_bigint('itemcustoid') }} as itemcustoid, + {{ safe_bigint('quantidadeitemcusto') }} as quantidadeitemcusto, + {{ safe_bigint('projetoid') }} as projetoid, + descricaoitemcusto::text as descricaoitemcusto, + {{ safe_date('datainicial') }} as datainicial, + {{ safe_date('datafinal') }} as datafinal, + {{ safe_numeric('custoespecifico') }} as custoespecifico + from {{ source("ipea_pro", "custosemprojetos") }} + ) + +select * +from custosemprojetos diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_diretorias.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_diretorias.sql new file mode 100644 index 00000000..dc97532e --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_diretorias.sql @@ -0,0 +1,17 @@ +{{ config(materialized="table") }} + +with + diretorias as ( + select + {{ safe_bigint('diretoriaid') }} as diretoriaid, + diretorianome::text as diretorianome, + diretoriasigla::text as diretoriasigla, + diretoriadescricao::text as diretoriadescricao, + {{ safe_bigint('diretorid') }} as diretorid, + {{ safe_bigint('diretoradjuntoid') }} as diretoradjuntoid, + codigosiape::text as codigosiape + from {{ source("ipea_pro", "diretorias") }} + ) + +select * +from diretorias diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_fontesreceitas.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_fontesreceitas.sql new file mode 100644 index 00000000..3ac5fb9c --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_fontesreceitas.sql @@ -0,0 +1,18 @@ +{{ config(materialized="table") }} + +with + fontesreceitas as ( + select + {{ safe_bigint('fontereceitaid') }} as fontereceitaid, + {{ safe_bigint('projetoid') }} as projetoid, + {{ safe_bigint('itemfontereceitaid') }} as itemfontereceitaid, + descricaofonte::text as descricaofonte, + {{ safe_numeric('valortotalfonte') }} as valortotalfonte, + {{ safe_date('datainiciofonte') }} as datainiciofonte, + {{ safe_date('datafinalfonte') }} as datafinalfonte, + {{ safe_numeric('valortotalfonte_bkp') }} as valortotalfonte_bkp + from {{ source("ipea_pro", "fontesreceitas") }} + ) + +select * +from fontesreceitas diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_grupoentidade.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_grupoentidade.sql new file mode 100644 index 00000000..11193ee2 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_grupoentidade.sql @@ -0,0 +1,12 @@ +{{ config(materialized="table") }} + +with + grupoentidade as ( + select + {{ safe_bigint('grupoentidadeid') }} as grupoentidadeid, + nome::text as nome + from {{ source("ipea_pro", "grupoentidade") }} + ) + +select * +from grupoentidade diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_insumofinanceiro.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_insumofinanceiro.sql new file mode 100644 index 00000000..3fa20feb --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_insumofinanceiro.sql @@ -0,0 +1,16 @@ +{{ config(materialized="table") }} + +with + insumofinanceiro as ( + select + {{ safe_bigint('insumofinanceiroid') }} as insumofinanceiroid, + item::text as item, + unidade::text as unidade, + descricao::text as descricao, + {{ safe_boolean('possuivalorunitario') }} as possuivalorunitario, + {{ safe_boolean('possuiquantidade') }} as possuiquantidade + from {{ source("ipea_pro", "insumofinanceiro") }} + ) + +select * +from insumofinanceiro diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_itemfontereceitas.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_itemfontereceitas.sql new file mode 100644 index 00000000..923d0352 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_itemfontereceitas.sql @@ -0,0 +1,14 @@ +{{ config(materialized="table") }} + +with + itemfontereceitas as ( + select + {{ safe_bigint('itemfontereceitaid') }} as itemfontereceitaid, + nomeitemfontereceita::text as nomeitemfontereceita, + descricaoitemfontereceita::text as descricaoitemfontereceita, + observacao::text as observacao + from {{ source("ipea_pro", "itemfontereceitas") }} + ) + +select * +from itemfontereceitas diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_itenscustos.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_itenscustos.sql new file mode 100644 index 00000000..54cecd46 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_itenscustos.sql @@ -0,0 +1,20 @@ +{{ config(materialized="table") }} + +with + itenscustos as ( + select + {{ safe_bigint('itemcustoid') }} as itemcustoid, + nomeitem::text as nomeitem, + descricaoitem::text as descricaoitem, + unidadeitem::text as unidadeitem, + {{ safe_numeric('valorunitarioitem') }} as valorunitarioitem, + {{ safe_bigint('statusitemcadastradoid') }} as statusitemcadastradoid, + {{ safe_bigint('categoriasitenscustoid') }} as categoriasitenscustoid, + {{ safe_bigint('ordem') }} as ordem, + {{ safe_boolean('ativo') }} as ativo, + {{ safe_boolean('padrao') }} as padrao + from {{ source("ipea_pro", "itenscustos") }} + ) + +select * +from itenscustos diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_projetos.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_projetos.sql new file mode 100644 index 00000000..fe189678 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_projetos.sql @@ -0,0 +1,44 @@ +{{ config(materialized="table") }} + +with + projetos as ( + select + {{ safe_bigint('projetoid') }} as projetoid, + {{ safe_bigint('categoriaadicionalprojetoid') }} as categoriaadicionalprojetoid, + tituloprojeto::text as tituloprojeto, + objetivofinalprojeto::text as objetivofinalprojeto, + justificativaprojeto::text as justificativaprojeto, + metodologiaprojeto::text as metodologiaprojeto, + cooperacao::text as cooperacao, + {{ safe_date('datainicio') }} as datainicio, + {{ safe_date('datafim') }} as datafim, + {{ safe_bigint('coordenadorid') }} as coordenadorid, + {{ safe_bigint('diretoriaid') }} as diretoriaid, + {{ safe_bigint('coordenacaoid') }} as coordenacaoid, + {{ safe_bigint('statusetapaid') }} as statusetapaid, + {{ safe_bigint('statusprojetoid') }} as statusprojetoid, + {{ safe_bigint('modalidadeprojetoid') }} as modalidadeprojetoid, + numeroprojeto::text as numeroprojeto, + numeroprojetonoano::text as numeroprojetonoano, + {{ safe_bigint('anoprojeto') }} as anoprojeto, + {{ safe_bigint('usuariocadastranteid') }} as usuariocadastranteid, + {{ safe_bigint('usuarioversaoid') }} as usuarioversaoid, + {{ safe_bigint('numeroversao') }} as numeroversao, + {{ safe_timestamp('horaversao') }} as horaversao, + {{ safe_boolean('projetoexcluido') }} as projetoexcluido, + {{ safe_bigint('usuarioexcluinteid') }} as usuarioexcluinteid, + {{ safe_bigint('rowversion') }} as rowversion, + {{ safe_boolean('projetoassessoria') }} as projetoassessoria, + {{ safe_boolean('projetoestrategico') }} as projetoestrategico, + {{ safe_date('datafimreal') }} as datafimreal, + {{ safe_bigint('statusprojetohistoricoid') }} as statusprojetohistoricoid, + palavras_chave::text as palavras_chave, + planocomunicacao::text as planocomunicacao, + justificativaprojetoestrategico::text as justificativaprojetoestrategico, + {{ safe_boolean('planejamentofinanceiroativo') }} as planejamentofinanceiroativo, + {{ safe_boolean('projetoprioritario') }} as projetoprioritario + from {{ source("ipea_pro", "projetos") }} + ) + +select * +from projetos diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_registrofinanceiroemprojetos.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_registrofinanceiroemprojetos.sql new file mode 100644 index 00000000..e7367edd --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_registrofinanceiroemprojetos.sql @@ -0,0 +1,18 @@ +{{ config(materialized="table") }} + +with + registrofinanceiroemprojetos as ( + select + {{ safe_bigint('registrofinanceiroid') }} as registrofinanceiroid, + descricaoinsumo::text as descricaoinsumo, + {{ safe_numeric('valorunitarioinsumo') }} as valorunitarioinsumo, + {{ safe_bigint('grupoentidadeid') }} as grupoentidadeid, + {{ safe_bigint('insumofinanceiroid') }} as insumofinanceiroid, + {{ safe_bigint('projetoid') }} as projetoid, + {{ safe_bigint('quantidadeinsumo') }} as quantidadeinsumo, + descricaofonte::text as descricaofonte + from {{ source("ipea_pro", "registrofinanceiroemprojetos") }} + ) + +select * +from registrofinanceiroemprojetos diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_servidorespublicos.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_servidorespublicos.sql new file mode 100644 index 00000000..5c71a323 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_servidorespublicos.sql @@ -0,0 +1,57 @@ +{{ config(materialized="table") }} + +with + servidorespublicos as ( + select + {{ safe_bigint('servidorpublicoid') }} as servidorpublicoid, + {{ safe_bigint('categoriaadicionalservidorid') }} as categoriaadicionalservidorid, + nomeservidor::text as nomeservidor, + {{ safe_date('datanascimento') }} as datanascimento, + regexp_replace(numerocpf, '[^0-9]', '', 'g')::text as numerocpf, + numerorg::text as numerorg, + expedidorrg::text as expedidorrg, + {{ safe_bigint('diretoriaid') }} as diretoriaid, + {{ safe_bigint('coordenacaoid') }} as coordenacaoid, + {{ safe_numeric('salarioatual') }} as salarioatual, + {{ safe_numeric('custodoservidor') }} as custodoservidor, + email1::text as email1, + email2::text as email2, + email3::text as email3, + telefone1::text as telefone1, + telefone2::text as telefone2, + telefone3::text as telefone3, + {{ safe_bigint('unidadeipeaid') }} as unidadeipeaid, + regexp_replace(residenciacep, '[^0-9]', '', 'g')::text as residenciacep, + residenciarua::text as residenciarua, + residencianumero::text as residencianumero, + residenciacomplemento::text as residenciacomplemento, + residenciabairro::text as residenciabairro, + servidorminicv::text as servidorminicv, + linktolattes::text as linktolattes, + {{ safe_bigint('ufid') }} as ufid, + {{ safe_bigint('municipioid') }} as municipioid, + {{ safe_bigint('dasid') }} as dasid, + {{ safe_bigint('categoriaid') }} as categoriaid, + matriculasiape::text as matriculasiape, + {{ safe_bigint('sexoid') }} as sexoid, + {{ safe_bigint('statuscadastroid') }} as statuscadastroid, + {{ safe_bigint('situacaoservidorid') }} as situacaoservidorid, + login::text as login, + senha::text as senha, + {{ safe_bigint('usuarioid') }} as usuarioid, + {{ safe_bigint('idnosistemalegado') }} as idnosistemalegado, + {{ safe_bigint('instituicaoid') }} as instituicaoid, + regexp_replace(trabalhocep, '[^0-9]', '', 'g')::text as trabalhocep, + trabalhorua::text as trabalhorua, + trabalhonumero::text as trabalhonumero, + trabalhocomplemento::text as trabalhocomplemento, + trabalhobairro::text as trabalhobairro, + {{ safe_bigint('trabalhoufid') }} as trabalhoufid, + {{ safe_bigint('trabalhomunicipioid') }} as trabalhomunicipioid, + {{ safe_bigint('cargocomissaoid') }} as cargocomissaoid, + ramal::text as ramal + from {{ source("ipea_pro", "servidorespublicos") }} + ) + +select * +from servidorespublicos diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_statusprojetos.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_statusprojetos.sql new file mode 100644 index 00000000..5f8c41ee --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/ipea_pro_statusprojetos.sql @@ -0,0 +1,14 @@ +{{ config(materialized="table") }} + +with + statusprojetos as ( + select + {{ safe_bigint('statusprojetoid') }} as statusprojetoid, + nomestatus::text as nomestatus, + descricaostatus::text as descricaostatus, + {{ safe_boolean('ativo') }} as ativo + from {{ source("ipea_pro", "statusprojetos") }} + ) + +select * +from statusprojetos diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/schema.yml b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/schema.yml new file mode 100644 index 00000000..274a6d98 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/schema.yml @@ -0,0 +1,1172 @@ +version: 2 + +models: + - name: ipea_pro_bolsistas + description: > + Tabela bronze com o cadastro de bolsistas do sistema Sisbolsas. + Reúne dados de identificação pessoal, vínculos administrativos, contatos, + endereço residencial, referências ao projeto e metadados de autenticação + existentes na base operacional do `ipea_pro`. + Nesta camada, as colunas foram tipadas e padronizadas para apoiar análises + e integrações posteriores. + meta: + tags: + - bronze + columns: + - name: bolsistaid + description: Identificador único do bolsista no Sisbolsas. + - name: categoriaadicionalbolsistaid + description: Identificador da categoria adicional associada ao bolsista. + - name: nomebolsista + description: Nome completo do bolsista. + - name: datanascimento + description: Data de nascimento do bolsista. + - name: numerocpf + description: CPF do bolsista, padronizado apenas com dígitos. + - name: numerorg + description: Número do documento de RG do bolsista. + - name: expedidorrg + description: Órgão expedidor do RG do bolsista. + - name: coordenadorid + description: Identificador do coordenador associado ao bolsista. + - name: diretoriaid + description: Identificador da diretoria vinculada ao bolsista. + - name: coordenacaoid + description: Identificador da coordenação vinculada ao bolsista. + - name: unidadeipeaid + description: Identificador da unidade do IPEA associada ao bolsista. + - name: email1 + description: Endereço de e-mail principal do bolsista. + - name: email2 + description: Endereço de e-mail secundário do bolsista. + - name: email3 + description: Endereço de e-mail adicional do bolsista. + - name: telefone1 + description: Telefone principal de contato do bolsista. + - name: telefone2 + description: Telefone secundário de contato do bolsista. + - name: telefone3 + description: Telefone adicional de contato do bolsista. + - name: residenciacep + description: CEP do endereço residencial do bolsista, padronizado apenas com dígitos. + - name: residenciarua + description: Nome da rua ou logradouro residencial do bolsista. + - name: residencianumero + description: Número do endereço residencial do bolsista. + - name: residenciacomplemento + description: Complemento do endereço residencial do bolsista. + - name: residenciabairro + description: Bairro do endereço residencial do bolsista. + - name: bolsistaminicv + description: Minicurrículo ou resumo curricular do bolsista. + - name: linktolattes + description: Link para o currículo Lattes do bolsista. + - name: ufid + description: Identificador da unidade federativa associada ao bolsista. + - name: municipioid + description: Identificador do município associado ao bolsista. + - name: bolsadatainicio + description: Data de início da bolsa do bolsista. + - name: bolsadatafinalizacao + description: Data de finalização da bolsa do bolsista. + - name: categoriabolsistaid + description: Identificador da categoria principal da bolsa. + - name: projetoid + description: Identificador do projeto ao qual o bolsista está vinculado. + - name: sexoid + description: Identificador do sexo cadastrado para o bolsista. + - name: situacaopessoaid + description: Identificador da situação pessoal do bolsista. + - name: statuscadastroid + description: Identificador do status cadastral do bolsista. + - name: custoemprojetoid + description: Identificador do custo do bolsista dentro do projeto. + - name: login + description: Login de acesso do bolsista no sistema. + - name: senha + description: Valor armazenado no campo de senha do cadastro do bolsista. + - name: usuarioid + description: Identificador do usuário relacionado ao cadastro do bolsista. + - name: idnosistemalegado + description: Identificador do bolsista no sistema legado. + - name: matriculaipea + description: Matrícula interna do bolsista no IPEA, quando existente. + - name: observacoes + description: Campo livre com observações complementares sobre o bolsista. + + - name: ipea_pro_coordenacoes + description: > + Tabela bronze com o cadastro de coordenações do sistema Sisbolsas. + Contém identificação, nome, sigla, descrição, responsável, vínculo com + diretoria e indicador de atividade da coordenação. + meta: + tags: + - bronze + columns: + - name: coordenacaoid + description: Identificador único da coordenação. + - name: coordenacaonome + description: Nome da coordenação. + - name: coordenacaosigla + description: Sigla da coordenação. + - name: coordenacaodescricao + description: Descrição textual da coordenação. + - name: coordenadorid + description: Identificador do coordenador responsável pela coordenação. + - name: diretoriaid + description: Identificador da diretoria à qual a coordenação está vinculada. + - name: ativa + description: Indica se a coordenação está ativa no sistema. + - name: codigosiape + description: Código SIAPE associado à coordenação, quando informado. + + - name: ipea_pro_custosemprojetos + description: > + Tabela bronze com os custos planejados ou registrados em projetos do + Sisbolsas. Armazena a relação entre projeto, item de custo, quantidade, + período de vigência e valor específico do custo. + meta: + tags: + - bronze + columns: + - name: custoemprojetoid + description: Identificador único do registro de custo em projeto. + - name: itemcustoid + description: Identificador do item de custo associado ao registro. + - name: quantidadeitemcusto + description: Quantidade prevista ou registrada do item de custo. + - name: projetoid + description: Identificador do projeto ao qual o custo está vinculado. + - name: descricaoitemcusto + description: Descrição complementar do item de custo no contexto do projeto. + - name: datainicial + description: Data inicial de vigência ou referência do custo no projeto. + - name: datafinal + description: Data final de vigência ou referência do custo no projeto. + - name: custoespecifico + description: Valor específico do custo registrado para o projeto. + + - name: ipea_pro_diretorias + description: > + Tabela bronze com o cadastro de diretorias do sistema Sisbolsas. + Reúne identificação, nome, sigla, descrição, dirigentes vinculados e + código SIAPE associado à estrutura administrativa. + meta: + tags: + - bronze + columns: + - name: diretoriaid + description: Identificador único da diretoria. + - name: diretorianome + description: Nome da diretoria. + - name: diretoriasigla + description: Sigla da diretoria. + - name: diretoriadescricao + description: Descrição textual da diretoria. + - name: diretorid + description: Identificador do diretor responsável pela diretoria. + - name: diretoradjuntoid + description: Identificador do diretor adjunto da diretoria. + - name: codigosiape + description: Código SIAPE associado à diretoria, quando disponível. + + - name: ipea_pro_fontesreceitas + description: > + Tabela bronze com o cadastro de fontes de receita vinculadas aos projetos + do Sisbolsas. Contém a referência do projeto, tipo de fonte, descrição, + valores e intervalo temporal da receita. + meta: + tags: + - bronze + columns: + - name: fontereceitaid + description: Identificador único da fonte de receita. + - name: projetoid + description: Identificador do projeto ao qual a fonte de receita está vinculada. + - name: itemfontereceitaid + description: Identificador do item de classificação da fonte de receita. + - name: descricaofonte + description: Descrição textual da fonte de receita. + - name: valortotalfonte + description: Valor total previsto ou registrado para a fonte de receita. + - name: datainiciofonte + description: Data de início da vigência da fonte de receita. + - name: datafinalfonte + description: Data de término da vigência da fonte de receita. + - name: valortotalfonte_bkp + description: Valor total da fonte registrado no campo de backup da base original. + + - name: ipea_pro_grupoentidade + description: > + Tabela bronze com o cadastro de grupos de entidade utilizados pelo + Sisbolsas para classificar registros financeiros ou institucionais. + meta: + tags: + - bronze + columns: + - name: grupoentidadeid + description: Identificador único do grupo de entidade. + - name: nome + description: Nome do grupo de entidade. + + - name: ipea_pro_insumofinanceiro + description: > + Tabela bronze com o cadastro de insumos financeiros do Sisbolsas. + Define itens financeiros, unidade de medida, descrição e indicadores sobre + a existência de valor unitário e quantidade. + meta: + tags: + - bronze + columns: + - name: insumofinanceiroid + description: Identificador único do insumo financeiro. + - name: item + description: Nome ou título do item de insumo financeiro. + - name: unidade + description: Unidade de medida associada ao insumo financeiro. + - name: descricao + description: Descrição detalhada do insumo financeiro. + - name: possuivalorunitario + description: Indica se o insumo financeiro possui valor unitário informado. + - name: possuiquantidade + description: Indica se o insumo financeiro possui quantidade informada. + + - name: ipea_pro_itemfontereceitas + description: > + Tabela bronze com os itens de classificação das fontes de receita do + Sisbolsas. Serve de apoio para categorizar e descrever os tipos de + receita utilizados nos projetos. + meta: + tags: + - bronze + columns: + - name: itemfontereceitaid + description: Identificador único do item de fonte de receita. + - name: nomeitemfontereceita + description: Nome do item de fonte de receita. + - name: descricaoitemfontereceita + description: Descrição detalhada do item de fonte de receita. + - name: observacao + description: Observações complementares sobre o item de fonte de receita. + + - name: ipea_pro_itenscustos + description: > + Tabela bronze com o cadastro de itens de custo utilizados no Sisbolsas. + Contém nome, descrição, unidade, valor unitário de referência, + classificação cadastral, ordenação e indicadores de atividade e padrão. + meta: + tags: + - bronze + columns: + - name: itemcustoid + description: Identificador único do item de custo. + - name: nomeitem + description: Nome do item de custo. + - name: descricaoitem + description: Descrição detalhada do item de custo. + - name: unidadeitem + description: Unidade de medida do item de custo. + - name: valorunitarioitem + description: Valor unitário de referência do item de custo. + - name: statusitemcadastradoid + description: Identificador do status cadastral do item de custo. + - name: categoriasitenscustoid + description: Identificador da categoria do item de custo. + - name: ordem + description: Ordem de exibição ou prioridade do item de custo. + - name: ativo + description: Indica se o item de custo está ativo no sistema. + - name: padrao + description: Indica se o item de custo é considerado padrão. + + - name: ipea_pro_projetos + description: > + Tabela bronze com o cadastro de projetos do sistema Sisbolsas. + Consolida informações de identificação, escopo, justificativa, metodologia, + datas, responsáveis, situação do projeto, versionamento e indicadores + gerenciais utilizados na operação do sistema. + meta: + tags: + - bronze + columns: + - name: projetoid + description: Identificador único do projeto. + - name: categoriaadicionalprojetoid + description: Identificador da categoria adicional associada ao projeto. + - name: tituloprojeto + description: Título do projeto. + - name: objetivofinalprojeto + description: Objetivo final declarado para o projeto. + - name: justificativaprojeto + description: Justificativa cadastrada para o projeto. + - name: metodologiaprojeto + description: Metodologia descrita para execução do projeto. + - name: cooperacao + description: Informação textual sobre cooperação relacionada ao projeto. + - name: datainicio + description: Data de início do projeto. + - name: datafim + description: Data prevista de término do projeto. + - name: coordenadorid + description: Identificador do coordenador responsável pelo projeto. + - name: diretoriaid + description: Identificador da diretoria vinculada ao projeto. + - name: coordenacaoid + description: Identificador da coordenação vinculada ao projeto. + - name: statusetapaid + description: Identificador do status da etapa do projeto. + - name: statusprojetoid + description: Identificador do status do projeto. + - name: modalidadeprojetoid + description: Identificador da modalidade do projeto. + - name: numeroprojeto + description: Número formal do projeto. + - name: numeroprojetonoano + description: Numeração sequencial do projeto dentro do ano. + - name: anoprojeto + description: Ano de referência do projeto. + - name: usuariocadastranteid + description: Identificador do usuário que cadastrou o projeto. + - name: usuarioversaoid + description: Identificador do usuário responsável pela versão do projeto. + - name: numeroversao + description: Número da versão do registro do projeto. + - name: horaversao + description: Data e hora de versionamento do registro do projeto. + - name: projetoexcluido + description: Indica se o projeto foi marcado como excluído. + - name: usuarioexcluinteid + description: Identificador do usuário que realizou a exclusão lógica do projeto. + - name: rowversion + description: Controle numérico de versão da linha na base de origem. + - name: projetoassessoria + description: Indica se o projeto é classificado como projeto de assessoria. + - name: projetoestrategico + description: Indica se o projeto é classificado como estratégico. + - name: datafimreal + description: Data real de encerramento do projeto, quando houver. + - name: statusprojetohistoricoid + description: Identificador do histórico de status do projeto. + - name: palavras_chave + description: Palavras-chave associadas ao projeto. + - name: planocomunicacao + description: Plano de comunicação vinculado ao projeto. + - name: justificativaprojetoestrategico + description: Justificativa para o enquadramento do projeto como estratégico. + - name: planejamentofinanceiroativo + description: Indica se o planejamento financeiro do projeto está ativo. + - name: projetoprioritario + description: Indica se o projeto foi marcado como prioritário. + + - name: ipea_pro_registrofinanceiroemprojetos + description: > + Tabela bronze com os registros financeiros vinculados aos projetos do + Sisbolsas. Relaciona projeto, insumo financeiro, grupo de entidade, + quantidade, valor unitário e descrição da fonte associada. + meta: + tags: + - bronze + columns: + - name: registrofinanceiroid + description: Identificador único do registro financeiro em projeto. + - name: descricaoinsumo + description: Descrição do insumo financeiro associado ao registro. + - name: valorunitarioinsumo + description: Valor unitário do insumo financeiro no registro. + - name: grupoentidadeid + description: Identificador do grupo de entidade associado ao registro. + - name: insumofinanceiroid + description: Identificador do insumo financeiro vinculado ao registro. + - name: projetoid + description: Identificador do projeto ao qual o registro financeiro pertence. + - name: quantidadeinsumo + description: Quantidade do insumo financeiro registrada no projeto. + - name: descricaofonte + description: Descrição da fonte de recursos associada ao registro financeiro. + + - name: ipea_pro_servidorespublicos + description: > + Tabela bronze com o cadastro de servidores públicos do sistema Sisbolsas. + Contém identificação pessoal, vínculos organizacionais, informações de + contato, endereço residencial e de trabalho, custo do servidor e dados de + autenticação presentes na base operacional. + meta: + tags: + - bronze + columns: + - name: servidorpublicoid + description: Identificador único do servidor público no Sisbolsas. + - name: categoriaadicionalservidorid + description: Identificador da categoria adicional associada ao servidor. + - name: nomeservidor + description: Nome completo do servidor público. + - name: datanascimento + description: Data de nascimento do servidor público. + - name: numerocpf + description: CPF do servidor público, padronizado apenas com dígitos. + - name: numerorg + description: Número do RG do servidor público. + - name: expedidorrg + description: Órgão expedidor do RG do servidor público. + - name: diretoriaid + description: Identificador da diretoria vinculada ao servidor. + - name: coordenacaoid + description: Identificador da coordenação vinculada ao servidor. + - name: salarioatual + description: Valor do salário atual do servidor. + - name: custodoservidor + description: Valor do custo total associado ao servidor. + - name: email1 + description: Endereço de e-mail principal do servidor. + - name: email2 + description: Endereço de e-mail secundário do servidor. + - name: email3 + description: Endereço de e-mail adicional do servidor. + - name: telefone1 + description: Telefone principal de contato do servidor. + - name: telefone2 + description: Telefone secundário de contato do servidor. + - name: telefone3 + description: Telefone adicional de contato do servidor. + - name: unidadeipeaid + description: Identificador da unidade do IPEA associada ao servidor. + - name: residenciacep + description: CEP do endereço residencial do servidor, padronizado apenas com dígitos. + - name: residenciarua + description: Rua ou logradouro do endereço residencial do servidor. + - name: residencianumero + description: Número do endereço residencial do servidor. + - name: residenciacomplemento + description: Complemento do endereço residencial do servidor. + - name: residenciabairro + description: Bairro do endereço residencial do servidor. + - name: servidorminicv + description: Minicurrículo ou resumo curricular do servidor. + - name: linktolattes + description: Link para o currículo Lattes do servidor. + - name: ufid + description: Identificador da unidade federativa associada ao servidor. + - name: municipioid + description: Identificador do município associado ao servidor. + - name: dasid + description: Identificador do DAS ou função comissionada associada ao servidor. + - name: categoriaid + description: Identificador da categoria principal do servidor. + - name: matriculasiape + description: Matrícula SIAPE do servidor. + - name: sexoid + description: Identificador do sexo cadastrado para o servidor. + - name: statuscadastroid + description: Identificador do status cadastral do servidor. + - name: situacaoservidorid + description: Identificador da situação do servidor no sistema. + - name: login + description: Login de acesso do servidor no sistema. + - name: senha + description: Valor armazenado no campo de senha do cadastro do servidor. + - name: usuarioid + description: Identificador do usuário relacionado ao cadastro do servidor. + - name: idnosistemalegado + description: Identificador do servidor no sistema legado. + - name: instituicaoid + description: Identificador da instituição vinculada ao servidor. + - name: trabalhocep + description: CEP do endereço de trabalho do servidor, padronizado apenas com dígitos. + - name: trabalhorua + description: Rua ou logradouro do endereço de trabalho do servidor. + - name: trabalhonumero + description: Número do endereço de trabalho do servidor. + - name: trabalhocomplemento + description: Complemento do endereço de trabalho do servidor. + - name: trabalhobairro + description: Bairro do endereço de trabalho do servidor. + - name: trabalhoufid + description: Identificador da unidade federativa do endereço de trabalho. + - name: trabalhomunicipioid + description: Identificador do município do endereço de trabalho. + - name: cargocomissaoid + description: Identificador do cargo em comissão associado ao servidor. + - name: ramal + description: Ramal telefônico do servidor. + + - name: ipea_pro_statusprojetos + description: > + Tabela bronze com o cadastro de status de projetos do Sisbolsas. + Funciona como dimensão de apoio para qualificar a situação dos projetos + registrados no sistema. + meta: + tags: + - bronze + columns: + - name: statusprojetoid + description: Identificador único do status de projeto. + - name: nomestatus + description: Nome do status do projeto. + - name: descricaostatus + description: Descrição detalhada do status do projeto. + - name: ativo + description: Indica se o status de projeto está ativo no sistema. + + - name: sisbolsas_tb_area_formacao + description: > + Tabela bronze com o cadastro de áreas de formação utilizadas pelo + Sisbolsas para classificar formações acadêmicas e perfis de seleção. + meta: + tags: + - bronze + columns: + - name: co_area_formacao + description: Código identificador registrado no campo de origem referente a `co area formacao`. + - name: ds_area_formacao + description: Descrição textual registrada no campo de origem referente a `ds area formacao`. + + - name: sisbolsas_tb_bolsa + description: > + Tabela bronze com o cadastro de bolsas vinculadas às seleções do + Sisbolsas, incluindo situação, período de vigência e duração prevista. + meta: + tags: + - bronze + columns: + - name: co_selecao + description: Código identificador registrado no campo de origem referente a `co selecao`. + - name: nu_bolsa + description: Número registrado no campo de origem referente a `nu bolsa`. + - name: co_situacao_bolsa + description: Código identificador registrado no campo de origem referente a `co situacao bolsa`. + - name: dt_inicio_bolsa + description: Data registrada no campo de origem referente a `dt inicio bolsa`. + - name: dt_fim_bolsa + description: Data registrada no campo de origem referente a `dt fim bolsa`. + - name: qt_duracao + description: Quantidade registrada no campo de origem referente a `qt duracao`. + + - name: sisbolsas_tb_bolsa_coordenador + description: > + Tabela bronze com a vinculação entre bolsas e coordenadores no + Sisbolsas, incluindo CPF, vigência e situação do vínculo. + meta: + tags: + - bronze + columns: + - name: co_bolsa_coordenador + description: Código identificador registrado no campo de origem referente a `co bolsa coordenador`. + - name: co_selecao + description: Código identificador registrado no campo de origem referente a `co selecao`. + - name: nu_bolsa + description: Número registrado no campo de origem referente a `nu bolsa`. + - name: ds_cpf + description: CPF associado ao registro, padronizado apenas com dígitos. + - name: dt_inicio_vigencia + description: Data registrada no campo de origem referente a `dt inicio vigencia`. + - name: dt_fim_vigencia + description: Data registrada no campo de origem referente a `dt fim vigencia`. + - name: st_situacao + description: Status registrado no campo de origem referente a `st situacao`. + + - name: sisbolsas_tb_bolsa_fonterecurso + description: > + Tabela bronze com a associação entre bolsas e fontes financeiras no + Sisbolsas, registrando vigência e situação da fonte de recurso. + meta: + tags: + - bronze + columns: + - name: co_bolsa_fonterecurso + description: Código identificador registrado no campo de origem referente a `co bolsa fonterecurso`. + - name: co_selecao + description: Código identificador registrado no campo de origem referente a `co selecao`. + - name: nu_bolsa + description: Número registrado no campo de origem referente a `nu bolsa`. + - name: co_fonte_financeira + description: Código identificador registrado no campo de origem referente a `co fonte financeira`. + - name: dt_fim_vigencia + description: Data registrada no campo de origem referente a `dt fim vigencia`. + - name: dt_inicio_vigencia + description: Data registrada no campo de origem referente a `dt inicio vigencia`. + - name: st_situacao + description: Status registrado no campo de origem referente a `st situacao`. + + - name: sisbolsas_tb_bolsista + description: > + Tabela bronze com os vínculos de bolsistas às seleções e bolsas do + Sisbolsas, incluindo situação, número SEI e período de vigência. + meta: + tags: + - bronze + columns: + - name: co_usuario + description: Código identificador registrado no campo de origem referente a `co usuario`. + - name: co_selecao + description: Código identificador registrado no campo de origem referente a `co selecao`. + - name: nu_bolsa + description: Número registrado no campo de origem referente a `nu bolsa`. + - name: co_situacao_bolsista + description: Código identificador registrado no campo de origem referente a `co situacao bolsista`. + - name: ds_numero_sei + description: Descrição textual registrada no campo de origem referente a `ds numero sei`. + - name: dt_inicio + description: Data registrada no campo de origem referente a `dt inicio`. + - name: dt_fim + description: Data registrada no campo de origem referente a `dt fim`. + + - name: sisbolsas_tb_chamada_publica + description: > + Tabela bronze com o cadastro de chamadas públicas do Sisbolsas, reunindo + programa, situação, cronograma, valores estimados e metadados da + chamada. + meta: + tags: + - bronze + columns: + - name: co_chamada_publica + description: Código identificador registrado no campo de origem referente a `co chamada publica`. + - name: co_projeto + description: Código identificador registrado no campo de origem referente a `co projeto`. + - name: co_situacao_chamada + description: Código identificador registrado no campo de origem referente a `co situacao chamada`. + - name: co_usuario_criacao + description: Código identificador registrado no campo de origem referente a `co usuario criacao`. + - name: co_programa + description: Código identificador registrado no campo de origem referente a `co programa`. + - name: nu_chamada_publica + description: Número registrado no campo de origem referente a `nu chamada publica`. + - name: nu_ano + description: Número registrado no campo de origem referente a `nu ano`. + - name: ds_chamada_publica + description: Descrição textual registrada no campo de origem referente a `ds chamada publica`. + - name: ds_numero_sei + description: Descrição textual registrada no campo de origem referente a `ds numero sei`. + - name: vl_global_estimado + description: Valor monetário registrado no campo de origem referente a `vl global estimado`. + - name: dt_ini_pesquisa + description: Data registrada no campo de origem referente a `dt ini pesquisa`. + - name: dt_fim_pesquisa + description: Data registrada no campo de origem referente a `dt fim pesquisa`. + - name: dt_publicacao_dou + description: Data registrada no campo de origem referente a `dt publicacao dou`. + - name: dt_previsao_resultado + description: Data registrada no campo de origem referente a `dt previsao resultado`. + - name: dt_ini_bolsa + description: Data registrada no campo de origem referente a `dt ini bolsa`. + - name: dt_criacao + description: Data registrada no campo de origem referente a `dt criacao`. + - name: dt_inicio_inscricao + description: Data registrada no campo de origem referente a `dt inicio inscricao`. + - name: dt_fim_inscricao + description: Data registrada no campo de origem referente a `dt fim inscricao`. + - name: dt_inicio_julgamento + description: Data registrada no campo de origem referente a `dt inicio julgamento`. + - name: dt_fim_julgamento + description: Data registrada no campo de origem referente a `dt fim julgamento`. + - name: dt_publicacao_resultado + description: Data registrada no campo de origem referente a `dt publicacao resultado`. + - name: tp_moeda + description: Tipo registrado no campo de origem referente a `tp moeda`. + - name: dt_fim_recurso + description: Data registrada no campo de origem referente a `dt fim recurso`. + - name: dt_inicio_recurso + description: Data registrada no campo de origem referente a `dt inicio recurso`. + - name: dt_inicio_previsao_bolsa + description: Data registrada no campo de origem referente a `dt inicio previsao bolsa`. + + - name: sisbolsas_tb_chapubli_fontfina + description: > + Tabela bronze com a associação entre chamadas públicas e fontes + financeiras no Sisbolsas. + meta: + tags: + - bronze + columns: + - name: co_chamada_publica + description: Código identificador registrado no campo de origem referente a `co chamada publica`. + - name: co_fonte_financeira + description: Código identificador registrado no campo de origem referente a `co fonte financeira`. + + - name: sisbolsas_tb_chapubli_unidade + description: > + Tabela bronze com a associação entre chamadas públicas e unidades + organizacionais no Sisbolsas. + meta: + tags: + - bronze + columns: + - name: co_chamada_publica + description: Código identificador registrado no campo de origem referente a `co chamada publica`. + - name: co_unidade + description: Código identificador registrado no campo de origem referente a `co unidade`. + + - name: sisbolsas_tb_coordenador + description: > + Tabela bronze com a relação de coordenadores vinculados às chamadas + públicas do Sisbolsas. + meta: + tags: + - bronze + columns: + - name: co_chamada_publica + description: Código identificador registrado no campo de origem referente a `co chamada publica`. + - name: ds_cpf + description: CPF associado ao registro, padronizado apenas com dígitos. + + - name: sisbolsas_tb_dado_formacao + description: > + Tabela bronze com os dados de formação acadêmica cadastrados para + usuários do Sisbolsas, incluindo curso, instituição, área e validação do + diploma. + meta: + tags: + - bronze + columns: + - name: co_dado_formacao + description: Código identificador registrado no campo de origem referente a `co dado formacao`. + - name: co_usuario + description: Código identificador registrado no campo de origem referente a `co usuario`. + - name: co_nivel_escolaridade + description: Código identificador registrado no campo de origem referente a `co nivel escolaridade`. + - name: co_tipo_escolar + description: Código identificador registrado no campo de origem referente a `co tipo escolar`. + - name: st_nivel_escolaridade + description: Status registrado no campo de origem referente a `st nivel escolaridade`. + - name: tp_nacionalidade_diploma + description: Tipo registrado no campo de origem referente a `tp nacionalidade diploma`. + - name: in_diploma_valido + description: Indicador lógico registrado no campo de origem referente a `in diploma valido`. + - name: co_area_formacao + description: Código identificador registrado no campo de origem referente a `co area formacao`. + - name: ds_outra_formacao + description: Descrição textual registrada no campo de origem referente a `ds outra formacao`. + - name: ds_curso + description: Descrição textual registrada no campo de origem referente a `ds curso`. + - name: ds_instituicao + description: Descrição textual registrada no campo de origem referente a `ds instituicao`. + - name: co_estado + description: Código identificador registrado no campo de origem referente a `co estado`. + - name: ds_url_curriculo + description: Descrição textual registrada no campo de origem referente a `ds url curriculo`. + - name: dt_criacao + description: Data registrada no campo de origem referente a `dt criacao`. + - name: co_pais + description: Código identificador registrado no campo de origem referente a `co pais`. + + - name: sisbolsas_tb_dado_pessoal + description: > + Tabela bronze com os dados pessoais dos usuários do Sisbolsas, incluindo + identificação civil, contatos, nacionalidade e informações + complementares. + meta: + tags: + - bronze + columns: + - name: co_dado_pessoal + description: Código identificador registrado no campo de origem referente a `co dado pessoal`. + - name: co_usuario + description: Código identificador registrado no campo de origem referente a `co usuario`. + - name: ds_cpf + description: CPF associado ao registro, padronizado apenas com dígitos. + - name: dt_nascimento + description: Data registrada no campo de origem referente a `dt nascimento`. + - name: co_estado_civil + description: Código identificador registrado no campo de origem referente a `co estado civil`. + - name: tp_sexo + description: Tipo registrado no campo de origem referente a `tp sexo`. + - name: tp_nacionalidade + description: Tipo registrado no campo de origem referente a `tp nacionalidade`. + - name: ds_naturalidade + description: Descrição textual registrada no campo de origem referente a `ds naturalidade`. + - name: ds_rg + description: Descrição textual registrada no campo de origem referente a `ds rg`. + - name: dt_emissao_rg + description: Data registrada no campo de origem referente a `dt emissao rg`. + - name: ds_orgao_emissor + description: Descrição textual registrada no campo de origem referente a `ds orgao emissor`. + - name: co_estado_orgao + description: Código identificador registrado no campo de origem referente a `co estado orgao`. + - name: ds_passaporte + description: Descrição textual registrada no campo de origem referente a `ds passaporte`. + - name: tp_visto + description: Tipo registrado no campo de origem referente a `tp visto`. + - name: dt_validade_visto + description: Data registrada no campo de origem referente a `dt validade visto`. + - name: nu_telefone_principal + description: Número registrado no campo de origem referente a `nu telefone principal`. + - name: nu_telefone_alternativo + description: Número registrado no campo de origem referente a `nu telefone alternativo`. + - name: dt_criacao + description: Data registrada no campo de origem referente a `dt criacao`. + - name: co_pais_passaporte + description: Código identificador registrado no campo de origem referente a `co pais passaporte`. + - name: ds_ddd_principal + description: Descrição textual registrada no campo de origem referente a `ds ddd principal`. + - name: ds_ddd_alternativo + description: Descrição textual registrada no campo de origem referente a `ds ddd alternativo`. + - name: co_etnia + description: Código identificador registrado no campo de origem referente a `co etnia`. + - name: ds_email_alternativo + description: Descrição textual registrada no campo de origem referente a `ds email alternativo`. + - name: tp_fator + description: Tipo registrado no campo de origem referente a `tp fator`. + - name: tp_sanguineo + description: Tipo registrado no campo de origem referente a `tp sanguineo`. + + - name: sisbolsas_tb_dado_profissional + description: > + Tabela bronze com os dados profissionais dos usuários do Sisbolsas, + incluindo situação funcional, vínculo, instituição e cargo. + meta: + tags: + - bronze + columns: + - name: co_dado_profissional + description: Código identificador registrado no campo de origem referente a `co dado profissional`. + - name: tp_situacao_funcional + description: Tipo registrado no campo de origem referente a `tp situacao funcional`. + - name: in_vinculo + description: Indicador lógico registrado no campo de origem referente a `in vinculo`. + - name: tp_setor + description: Tipo registrado no campo de origem referente a `tp setor`. + - name: in_funcao_gratificada + description: Indicador lógico registrado no campo de origem referente a `in funcao gratificada`. + - name: ds_instituicao + description: Descrição textual registrada no campo de origem referente a `ds instituicao`. + - name: ds_empregador + description: Descrição textual registrada no campo de origem referente a `ds empregador`. + - name: tp_cargo + description: Tipo registrado no campo de origem referente a `tp cargo`. + - name: co_usuario + description: Código identificador registrado no campo de origem referente a `co usuario`. + - name: dt_criacao + description: Data registrada no campo de origem referente a `dt criacao`. + + - name: sisbolsas_tb_estado + description: > + Tabela bronze com o cadastro de estados utilizado pelo Sisbolsas para + referência territorial. + meta: + tags: + - bronze + columns: + - name: co_estado + description: Código identificador registrado no campo de origem referente a `co estado`. + - name: ds_uf + description: Descrição textual registrada no campo de origem referente a `ds uf`. + - name: ds_nome + description: Descrição textual registrada no campo de origem referente a `ds nome`. + + - name: sisbolsas_tb_folha_bolsista + description: > + Tabela bronze com os registros de pagamento de bolsistas em folhas do + Sisbolsas, incluindo valores pagos, quantidade de dias e conferência. + meta: + tags: + - bronze + columns: + - name: co_folha_pagamento + description: Código identificador registrado no campo de origem referente a `co folha pagamento`. + - name: co_usuario + description: Código identificador registrado no campo de origem referente a `co usuario`. + - name: co_selecao + description: Código identificador registrado no campo de origem referente a `co selecao`. + - name: nu_bolsa + description: Número registrado no campo de origem referente a `nu bolsa`. + - name: co_dado_bancario + description: Código identificador registrado no campo de origem referente a `co dado bancario`. + - name: co_diretoria + description: Código identificador registrado no campo de origem referente a `co diretoria`. + - name: co_unidade + description: Código identificador registrado no campo de origem referente a `co unidade`. + - name: co_fonte_financeira + description: Código identificador registrado no campo de origem referente a `co fonte financeira`. + - name: nu_dias_pago + description: Número registrado no campo de origem referente a `nu dias pago`. + - name: vl_dia_pago + description: Valor monetário registrado no campo de origem referente a `vl dia pago`. + - name: vl_total_pago + description: Valor monetário registrado no campo de origem referente a `vl total pago`. + - name: in_conferencia + description: Indicador lógico registrado no campo de origem referente a `in conferencia`. + + - name: sisbolsas_tb_folha_pagamento + description: > + Tabela bronze com o cadastro das folhas de pagamento do Sisbolsas, + contendo solicitação, lote, situação, totais e dados de homologação. + meta: + tags: + - bronze + columns: + - name: co_folha_pagamento + description: Código identificador registrado no campo de origem referente a `co folha pagamento`. + - name: nu_solicitacao + description: Número registrado no campo de origem referente a `nu solicitacao`. + - name: nu_mes + description: Número registrado no campo de origem referente a `nu mes`. + - name: nu_ano + description: Número registrado no campo de origem referente a `nu ano`. + - name: nu_lote + description: Número registrado no campo de origem referente a `nu lote`. + - name: co_situacao_folha + description: Código identificador registrado no campo de origem referente a `co situacao folha`. + - name: dt_criacao + description: Data registrada no campo de origem referente a `dt criacao`. + - name: nu_total_integral + description: Número registrado no campo de origem referente a `nu total integral`. + - name: nu_total_parcial + description: Número registrado no campo de origem referente a `nu total parcial`. + - name: co_usuario_criacao + description: Código identificador registrado no campo de origem referente a `co usuario criacao`. + - name: co_usuario_homologacao + description: Código identificador registrado no campo de origem referente a `co usuario homologacao`. + - name: dt_homologacao + description: Data registrada no campo de origem referente a `dt homologacao`. + + - name: sisbolsas_tb_fonte_financeira + description: > + Tabela bronze com o cadastro de fontes financeiras utilizadas pelo + Sisbolsas. + meta: + tags: + - bronze + columns: + - name: co_fonte_financeira + description: Código identificador registrado no campo de origem referente a `co fonte financeira`. + - name: ds_fonte_financeira + description: Descrição textual registrada no campo de origem referente a `ds fonte financeira`. + + - name: sisbolsas_tb_modalidade + description: > + Tabela bronze com o cadastro de modalidades de bolsa do Sisbolsas, + incluindo nível de escolaridade e período de vigência. + meta: + tags: + - bronze + columns: + - name: co_modalidade + description: Código identificador registrado no campo de origem referente a `co modalidade`. + - name: ds_modalidade + description: Descrição textual registrada no campo de origem referente a `ds modalidade`. + - name: co_nivel_escolaridade + description: Código identificador registrado no campo de origem referente a `co nivel escolaridade`. + - name: st_nivel_escolaridade + description: Status registrado no campo de origem referente a `st nivel escolaridade`. + - name: dt_inicio + description: Data registrada no campo de origem referente a `dt inicio`. + - name: dt_fim + description: Data registrada no campo de origem referente a `dt fim`. + + - name: sisbolsas_tb_nivel_escolaridade + description: > + Tabela bronze com o cadastro de níveis de escolaridade utilizados pelo + Sisbolsas. + meta: + tags: + - bronze + columns: + - name: co_nivel_escolaridade + description: Código identificador registrado no campo de origem referente a `co nivel escolaridade`. + - name: ds_nivel_escolaridade + description: Descrição textual registrada no campo de origem referente a `ds nivel escolaridade`. + + - name: sisbolsas_tb_processo_seletivo + description: > + Tabela bronze com os processos seletivos do Sisbolsas, relacionando + candidato, avaliação, classificação, situação de concessão e pontuações. + meta: + tags: + - bronze + columns: + - name: co_processo_seletivo + description: Código identificador registrado no campo de origem referente a `co processo seletivo`. + - name: co_usuario + description: Código identificador registrado no campo de origem referente a `co usuario`. + - name: co_dado_formacao + description: Código identificador registrado no campo de origem referente a `co dado formacao`. + - name: co_selecao + description: Código identificador registrado no campo de origem referente a `co selecao`. + - name: co_dado_profissional + description: Código identificador registrado no campo de origem referente a `co dado profissional`. + - name: co_situacao_concessao + description: Código identificador registrado no campo de origem referente a `co situacao concessao`. + - name: st_processo_seletivo + description: Status registrado no campo de origem referente a `st processo seletivo`. + - name: dt_criacao + description: Data registrada no campo de origem referente a `dt criacao`. + - name: in_nao_apto + description: Indicador lógico registrado no campo de origem referente a `in nao apto`. + - name: in_classificacao + description: Indicador lógico registrado no campo de origem referente a `in classificacao`. + - name: tx_observacao_avaliacao + description: Texto livre registrado no campo de origem referente a `tx observacao avaliacao`. + - name: tx_observacao_classificacao + description: Texto livre registrado no campo de origem referente a `tx observacao classificacao`. + - name: vl_total_criterio + description: Valor monetário registrado no campo de origem referente a `vl total criterio`. + - name: vl_total_geral + description: Valor monetário registrado no campo de origem referente a `vl total geral`. + - name: tx_observacao_entrevista + description: Texto livre registrado no campo de origem referente a `tx observacao entrevista`. + - name: ds_instituicao_bolsa + description: Descrição textual registrada no campo de origem referente a `ds instituicao bolsa`. + - name: nu_posicao + description: Número registrado no campo de origem referente a `nu posicao`. + - name: ds_token_aceite + description: Descrição textual registrada no campo de origem referente a `ds token aceite`. + - name: in_bolsa_ativa + description: Indicador lógico registrado no campo de origem referente a `in bolsa ativa`. + - name: ds_institu_bolsa_ativa + description: Descrição textual registrada no campo de origem referente a `ds institu bolsa ativa`. + - name: in_declaracao_veracidade + description: Indicador lógico registrado no campo de origem referente a `in declaracao veracidade`. + + - name: sisbolsas_tb_programa + description: > + Tabela bronze com o cadastro de programas do Sisbolsas, incluindo + hierarquia entre programas. + meta: + tags: + - bronze + columns: + - name: co_programa + description: Código identificador registrado no campo de origem referente a `co programa`. + - name: ds_programa + description: Descrição textual registrada no campo de origem referente a `ds programa`. + - name: co_programa_pai + description: Código identificador registrado no campo de origem referente a `co programa pai`. + + - name: sisbolsas_tb_selecao + description: > + Tabela bronze com o cadastro de seleções do Sisbolsas, contendo + modalidade, requisitos, valores, quantidade de bolsas e parâmetros de + avaliação. + meta: + tags: + - bronze + columns: + - name: co_selecao + description: Código identificador registrado no campo de origem referente a `co selecao`. + - name: co_chamada_publica + description: Código identificador registrado no campo de origem referente a `co chamada publica`. + - name: co_modalidade + description: Código identificador registrado no campo de origem referente a `co modalidade`. + - name: co_estado + description: Código identificador registrado no campo de origem referente a `co estado`. + - name: co_area_formacao + description: Código identificador registrado no campo de origem referente a `co area formacao`. + - name: co_nivel_escolaridade + description: Código identificador registrado no campo de origem referente a `co nivel escolaridade`. + - name: tp_atuacao + description: Tipo registrado no campo de origem referente a `tp atuacao`. + - name: st_nivel_escolaridade + description: Status registrado no campo de origem referente a `st nivel escolaridade`. + - name: qt_duracao + description: Quantidade registrada no campo de origem referente a `qt duracao`. + - name: vl_total + description: Valor monetário registrado no campo de origem referente a `vl total`. + - name: qt_bolsa + description: Quantidade registrada no campo de origem referente a `qt bolsa`. + - name: ds_area_especializacao + description: Descrição textual registrada no campo de origem referente a `ds area especializacao`. + - name: ds_outra_formacao + description: Descrição textual registrada no campo de origem referente a `ds outra formacao`. + - name: in_anexo_projeto + description: Indicador lógico registrado no campo de origem referente a `in anexo projeto`. + - name: in_anexo_idioma + description: Indicador lógico registrado no campo de origem referente a `in anexo idioma`. + - name: in_anexo_historico_escolar + description: Indicador lógico registrado no campo de origem referente a `in anexo historico escolar`. + - name: dt_criacao + description: Data registrada no campo de origem referente a `dt criacao`. + - name: co_usuario + description: Código identificador registrado no campo de origem referente a `co usuario`. + - name: co_cidade + description: Código identificador registrado no campo de origem referente a `co cidade`. + - name: nu_passo_avaliacao + description: Número registrado no campo de origem referente a `nu passo avaliacao`. + - name: st_avaliacao + description: Status registrado no campo de origem referente a `st avaliacao`. + - name: in_entrevista + description: Indicador lógico registrado no campo de origem referente a `in entrevista`. + - name: nu_selecao + description: Número registrado no campo de origem referente a `nu selecao`. + - name: in_anexo_outro + description: Indicador lógico registrado no campo de origem referente a `in anexo outro`. + - name: ds_anexo_outro + description: Descrição textual registrada no campo de origem referente a `ds anexo outro`. + + - name: sisbolsas_tb_situacao_chamada + description: > + Tabela bronze com o catálogo de situações possíveis para chamadas + públicas no Sisbolsas. + meta: + tags: + - bronze + columns: + - name: co_situacao_chamada + description: Código identificador registrado no campo de origem referente a `co situacao chamada`. + - name: ds_situacao_chamada + description: Descrição textual registrada no campo de origem referente a `ds situacao chamada`. + + - name: sisbolsas_tb_situacao_concessao + description: > + Tabela bronze com o catálogo de situações de concessão utilizadas nos + processos seletivos do Sisbolsas. + meta: + tags: + - bronze + columns: + - name: co_situacao_concessao + description: Código identificador registrado no campo de origem referente a `co situacao concessao`. + - name: ds_situacao_concessao + description: Descrição textual registrada no campo de origem referente a `ds situacao concessao`. + + - name: sisbolsas_tb_unidade + description: > + Tabela bronze com o cadastro de unidades organizacionais do Sisbolsas, + incluindo estado, sigla e diretoria associada. + meta: + tags: + - bronze + columns: + - name: co_unidade + description: Código identificador registrado no campo de origem referente a `co unidade`. + - name: ds_unidade + description: Descrição textual registrada no campo de origem referente a `ds unidade`. + - name: co_estado + description: Código identificador registrado no campo de origem referente a `co estado`. + - name: ds_sigla + description: Descrição textual registrada no campo de origem referente a `ds sigla`. + - name: co_diretoria + description: Código identificador registrado no campo de origem referente a `co diretoria`. + + - name: sisbolsas_tb_usuario + description: > + Tabela bronze com o cadastro de usuários do Sisbolsas, incluindo + identificação, login, e-mail, perfil, senha e dados de redefinição de + acesso. + meta: + tags: + - bronze + columns: + - name: co_usuario + description: Código identificador registrado no campo de origem referente a `co usuario`. + - name: ds_nome + description: Descrição textual registrada no campo de origem referente a `ds nome`. + - name: ds_login + description: Descrição textual registrada no campo de origem referente a `ds login`. + - name: co_perfil + description: Código identificador registrado no campo de origem referente a `co perfil`. + - name: ds_email + description: Descrição textual registrada no campo de origem referente a `ds email`. + - name: ds_senha + description: Descrição textual registrada no campo de origem referente a `ds senha`. + - name: dt_criacao + description: Data registrada no campo de origem referente a `dt criacao`. + - name: co_tipo_login + description: Código identificador registrado no campo de origem referente a `co tipo login`. + - name: st_usuario + description: Status registrado no campo de origem referente a `st usuario`. + - name: co_anexo + description: Código identificador registrado no campo de origem referente a `co anexo`. + - name: ds_token_redefinir_senha + description: Descrição textual registrada no campo de origem referente a `ds token redefinir senha`. + - name: dt_token_senha + description: Data registrada no campo de origem referente a `dt token senha`. diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_area_formacao.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_area_formacao.sql new file mode 100644 index 00000000..e119019e --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_area_formacao.sql @@ -0,0 +1,12 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_area_formacao as ( + select + co_area_formacao::text as co_area_formacao, + ds_area_formacao::text as ds_area_formacao + from {{ source("sisbolsas", "tb_area_formacao") }} + ) + +select * +from sisbolsas_tb_area_formacao diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_bolsa.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_bolsa.sql new file mode 100644 index 00000000..bb07cf7a --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_bolsa.sql @@ -0,0 +1,16 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_bolsa as ( + select + co_selecao::text as co_selecao, + nu_bolsa::text as nu_bolsa, + co_situacao_bolsa::text as co_situacao_bolsa, + dt_inicio_bolsa::text as dt_inicio_bolsa, + dt_fim_bolsa::text as dt_fim_bolsa, + qt_duracao::text as qt_duracao + from {{ source("sisbolsas", "tb_bolsa") }} + ) + +select * +from sisbolsas_tb_bolsa diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_bolsa_coordenador.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_bolsa_coordenador.sql new file mode 100644 index 00000000..bbf40411 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_bolsa_coordenador.sql @@ -0,0 +1,17 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_bolsa_coordenador as ( + select + co_bolsa_coordenador::text as co_bolsa_coordenador, + co_selecao::text as co_selecao, + nu_bolsa::text as nu_bolsa, + regexp_replace(ds_cpf, '[^0-9]', '', 'g')::text as ds_cpf, + dt_inicio_vigencia::text as dt_inicio_vigencia, + dt_fim_vigencia::text as dt_fim_vigencia, + st_situacao::text as st_situacao + from {{ source("sisbolsas", "tb_bolsa_coordenador") }} + ) + +select * +from sisbolsas_tb_bolsa_coordenador diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_bolsa_fonterecurso.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_bolsa_fonterecurso.sql new file mode 100644 index 00000000..c944d5b4 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_bolsa_fonterecurso.sql @@ -0,0 +1,17 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_bolsa_fonterecurso as ( + select + co_bolsa_fonterecurso::text as co_bolsa_fonterecurso, + co_selecao::text as co_selecao, + nu_bolsa::text as nu_bolsa, + co_fonte_financeira::text as co_fonte_financeira, + dt_fim_vigencia::text as dt_fim_vigencia, + dt_inicio_vigencia::text as dt_inicio_vigencia, + st_situacao::text as st_situacao + from {{ source("sisbolsas", "tb_bolsa_fonterecurso") }} + ) + +select * +from sisbolsas_tb_bolsa_fonterecurso diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_bolsista.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_bolsista.sql new file mode 100644 index 00000000..96db66ae --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_bolsista.sql @@ -0,0 +1,17 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_bolsista as ( + select + co_usuario::text as co_usuario, + co_selecao::text as co_selecao, + nu_bolsa::text as nu_bolsa, + co_situacao_bolsista::text as co_situacao_bolsista, + ds_numero_sei::text as ds_numero_sei, + dt_inicio::text as dt_inicio, + dt_fim::text as dt_fim + from {{ source("sisbolsas", "tb_bolsista") }} + ) + +select * +from sisbolsas_tb_bolsista diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_chamada_publica.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_chamada_publica.sql new file mode 100644 index 00000000..355aa901 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_chamada_publica.sql @@ -0,0 +1,35 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_chamada_publica as ( + select + co_chamada_publica::text as co_chamada_publica, + co_projeto::text as co_projeto, + co_situacao_chamada::text as co_situacao_chamada, + co_usuario_criacao::text as co_usuario_criacao, + co_programa::text as co_programa, + nu_chamada_publica::text as nu_chamada_publica, + nu_ano::text as nu_ano, + ds_chamada_publica::text as ds_chamada_publica, + ds_numero_sei::text as ds_numero_sei, + {{ safe_numeric('vl_global_estimado') }} as vl_global_estimado, + dt_ini_pesquisa::text as dt_ini_pesquisa, + dt_fim_pesquisa::text as dt_fim_pesquisa, + dt_publicacao_dou::text as dt_publicacao_dou, + dt_previsao_resultado::text as dt_previsao_resultado, + dt_ini_bolsa::text as dt_ini_bolsa, + dt_criacao::text as dt_criacao, + dt_inicio_inscricao::text as dt_inicio_inscricao, + dt_fim_inscricao::text as dt_fim_inscricao, + dt_inicio_julgamento::text as dt_inicio_julgamento, + dt_fim_julgamento::text as dt_fim_julgamento, + dt_publicacao_resultado::text as dt_publicacao_resultado, + tp_moeda::text as tp_moeda, + dt_fim_recurso::text as dt_fim_recurso, + dt_inicio_recurso::text as dt_inicio_recurso, + dt_inicio_previsao_bolsa::text as dt_inicio_previsao_bolsa + from {{ source("sisbolsas", "tb_chamada_publica") }} + ) + +select * +from sisbolsas_tb_chamada_publica diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_chapubli_fontfina.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_chapubli_fontfina.sql new file mode 100644 index 00000000..f397e676 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_chapubli_fontfina.sql @@ -0,0 +1,12 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_chapubli_fontfina as ( + select + co_chamada_publica::text as co_chamada_publica, + co_fonte_financeira::text as co_fonte_financeira + from {{ source("sisbolsas", "tb_chapubli_fontfina") }} + ) + +select * +from sisbolsas_tb_chapubli_fontfina diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_chapubli_unidade.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_chapubli_unidade.sql new file mode 100644 index 00000000..c2b51d46 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_chapubli_unidade.sql @@ -0,0 +1,12 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_chapubli_unidade as ( + select + co_chamada_publica::text as co_chamada_publica, + co_unidade::text as co_unidade + from {{ source("sisbolsas", "tb_chapubli_unidade") }} + ) + +select * +from sisbolsas_tb_chapubli_unidade diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_coordenador.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_coordenador.sql new file mode 100644 index 00000000..c9476f3f --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_coordenador.sql @@ -0,0 +1,12 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_coordenador as ( + select + co_chamada_publica::text as co_chamada_publica, + regexp_replace(ds_cpf, '[^0-9]', '', 'g')::text as ds_cpf + from {{ source("sisbolsas", "tb_coordenador") }} + ) + +select * +from sisbolsas_tb_coordenador diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_dado_formacao.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_dado_formacao.sql new file mode 100644 index 00000000..fd5d45ce --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_dado_formacao.sql @@ -0,0 +1,25 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_dado_formacao as ( + select + co_dado_formacao::text as co_dado_formacao, + co_usuario::text as co_usuario, + co_nivel_escolaridade::text as co_nivel_escolaridade, + co_tipo_escolar::text as co_tipo_escolar, + st_nivel_escolaridade::text as st_nivel_escolaridade, + tp_nacionalidade_diploma::text as tp_nacionalidade_diploma, + {{ safe_boolean('in_diploma_valido') }} as in_diploma_valido, + co_area_formacao::text as co_area_formacao, + ds_outra_formacao::text as ds_outra_formacao, + ds_curso::text as ds_curso, + ds_instituicao::text as ds_instituicao, + co_estado::text as co_estado, + ds_url_curriculo::text as ds_url_curriculo, + dt_criacao::text as dt_criacao, + co_pais::text as co_pais + from {{ source("sisbolsas", "tb_dado_formacao") }} + ) + +select * +from sisbolsas_tb_dado_formacao diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_dado_pessoal.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_dado_pessoal.sql new file mode 100644 index 00000000..c2cc1e85 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_dado_pessoal.sql @@ -0,0 +1,35 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_dado_pessoal as ( + select + co_dado_pessoal::text as co_dado_pessoal, + co_usuario::text as co_usuario, + regexp_replace(ds_cpf, '[^0-9]', '', 'g')::text as ds_cpf, + dt_nascimento::text as dt_nascimento, + co_estado_civil::text as co_estado_civil, + tp_sexo::text as tp_sexo, + tp_nacionalidade::text as tp_nacionalidade, + ds_naturalidade::text as ds_naturalidade, + ds_rg::text as ds_rg, + dt_emissao_rg::text as dt_emissao_rg, + ds_orgao_emissor::text as ds_orgao_emissor, + co_estado_orgao::text as co_estado_orgao, + ds_passaporte::text as ds_passaporte, + tp_visto::text as tp_visto, + dt_validade_visto::text as dt_validade_visto, + nu_telefone_principal::text as nu_telefone_principal, + nu_telefone_alternativo::text as nu_telefone_alternativo, + dt_criacao::text as dt_criacao, + co_pais_passaporte::text as co_pais_passaporte, + ds_ddd_principal::text as ds_ddd_principal, + ds_ddd_alternativo::text as ds_ddd_alternativo, + co_etnia::text as co_etnia, + ds_email_alternativo::text as ds_email_alternativo, + tp_fator::text as tp_fator, + tp_sanguineo::text as tp_sanguineo + from {{ source("sisbolsas", "tb_dado_pessoal") }} + ) + +select * +from sisbolsas_tb_dado_pessoal diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_dado_profissional.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_dado_profissional.sql new file mode 100644 index 00000000..2d5515b3 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_dado_profissional.sql @@ -0,0 +1,20 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_dado_profissional as ( + select + co_dado_profissional::text as co_dado_profissional, + tp_situacao_funcional::text as tp_situacao_funcional, + {{ safe_boolean('in_vinculo') }} as in_vinculo, + tp_setor::text as tp_setor, + {{ safe_boolean('in_funcao_gratificada') }} as in_funcao_gratificada, + ds_instituicao::text as ds_instituicao, + ds_empregador::text as ds_empregador, + tp_cargo::text as tp_cargo, + co_usuario::text as co_usuario, + dt_criacao::text as dt_criacao + from {{ source("sisbolsas", "tb_dado_profissional") }} + ) + +select * +from sisbolsas_tb_dado_profissional diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_estado.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_estado.sql new file mode 100644 index 00000000..f678150e --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_estado.sql @@ -0,0 +1,13 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_estado as ( + select + co_estado::text as co_estado, + ds_uf::text as ds_uf, + ds_nome::text as ds_nome + from {{ source("sisbolsas", "tb_estado") }} + ) + +select * +from sisbolsas_tb_estado diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_folha_bolsista.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_folha_bolsista.sql new file mode 100644 index 00000000..0092303e --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_folha_bolsista.sql @@ -0,0 +1,22 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_folha_bolsista as ( + select + co_folha_pagamento::text as co_folha_pagamento, + co_usuario::text as co_usuario, + co_selecao::text as co_selecao, + nu_bolsa::text as nu_bolsa, + co_dado_bancario::text as co_dado_bancario, + co_diretoria::text as co_diretoria, + co_unidade::text as co_unidade, + co_fonte_financeira::text as co_fonte_financeira, + nu_dias_pago::text as nu_dias_pago, + {{ safe_numeric('vl_dia_pago') }} as vl_dia_pago, + {{ safe_numeric('vl_total_pago') }} as vl_total_pago, + {{ safe_boolean('in_conferencia') }} as in_conferencia + from {{ source("sisbolsas", "tb_folha_bolsista") }} + ) + +select * +from sisbolsas_tb_folha_bolsista diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_folha_pagamento.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_folha_pagamento.sql new file mode 100644 index 00000000..1bfcd808 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_folha_pagamento.sql @@ -0,0 +1,22 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_folha_pagamento as ( + select + co_folha_pagamento::text as co_folha_pagamento, + nu_solicitacao::text as nu_solicitacao, + nu_mes::text as nu_mes, + nu_ano::text as nu_ano, + nu_lote::text as nu_lote, + co_situacao_folha::text as co_situacao_folha, + dt_criacao::text as dt_criacao, + nu_total_integral::text as nu_total_integral, + nu_total_parcial::text as nu_total_parcial, + co_usuario_criacao::text as co_usuario_criacao, + co_usuario_homologacao::text as co_usuario_homologacao, + dt_homologacao::text as dt_homologacao + from {{ source("sisbolsas", "tb_folha_pagamento") }} + ) + +select * +from sisbolsas_tb_folha_pagamento diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_fonte_financeira.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_fonte_financeira.sql new file mode 100644 index 00000000..3c285f7c --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_fonte_financeira.sql @@ -0,0 +1,12 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_fonte_financeira as ( + select + co_fonte_financeira::text as co_fonte_financeira, + ds_fonte_financeira::text as ds_fonte_financeira + from {{ source("sisbolsas", "tb_fonte_financeira") }} + ) + +select * +from sisbolsas_tb_fonte_financeira diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_modalidade.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_modalidade.sql new file mode 100644 index 00000000..98a13a9b --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_modalidade.sql @@ -0,0 +1,16 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_modalidade as ( + select + co_modalidade::text as co_modalidade, + ds_modalidade::text as ds_modalidade, + co_nivel_escolaridade::text as co_nivel_escolaridade, + st_nivel_escolaridade::text as st_nivel_escolaridade, + dt_inicio::text as dt_inicio, + dt_fim::text as dt_fim + from {{ source("sisbolsas", "tb_modalidade") }} + ) + +select * +from sisbolsas_tb_modalidade diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_nivel_escolaridade.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_nivel_escolaridade.sql new file mode 100644 index 00000000..9741b6f4 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_nivel_escolaridade.sql @@ -0,0 +1,12 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_nivel_escolaridade as ( + select + co_nivel_escolaridade::text as co_nivel_escolaridade, + ds_nivel_escolaridade::text as ds_nivel_escolaridade + from {{ source("sisbolsas", "tb_nivel_escolaridade") }} + ) + +select * +from sisbolsas_tb_nivel_escolaridade diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_processo_seletivo.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_processo_seletivo.sql new file mode 100644 index 00000000..4601551c --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_processo_seletivo.sql @@ -0,0 +1,31 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_processo_seletivo as ( + select + co_processo_seletivo::text as co_processo_seletivo, + co_usuario::text as co_usuario, + co_dado_formacao::text as co_dado_formacao, + co_selecao::text as co_selecao, + co_dado_profissional::text as co_dado_profissional, + co_situacao_concessao::text as co_situacao_concessao, + st_processo_seletivo::text as st_processo_seletivo, + dt_criacao::text as dt_criacao, + {{ safe_boolean('in_nao_apto') }} as in_nao_apto, + {{ safe_boolean('in_classificacao') }} as in_classificacao, + tx_observacao_avaliacao::text as tx_observacao_avaliacao, + tx_observacao_classificacao::text as tx_observacao_classificacao, + {{ safe_numeric('vl_total_criterio') }} as vl_total_criterio, + {{ safe_numeric('vl_total_geral') }} as vl_total_geral, + tx_observacao_entrevista::text as tx_observacao_entrevista, + ds_instituicao_bolsa::text as ds_instituicao_bolsa, + nu_posicao::text as nu_posicao, + ds_token_aceite::text as ds_token_aceite, + {{ safe_boolean('in_bolsa_ativa') }} as in_bolsa_ativa, + ds_institu_bolsa_ativa::text as ds_institu_bolsa_ativa, + {{ safe_boolean('in_declaracao_veracidade') }} as in_declaracao_veracidade + from {{ source("sisbolsas", "tb_processo_seletivo") }} + ) + +select * +from sisbolsas_tb_processo_seletivo diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_programa.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_programa.sql new file mode 100644 index 00000000..40cda55f --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_programa.sql @@ -0,0 +1,13 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_programa as ( + select + co_programa::text as co_programa, + ds_programa::text as ds_programa, + co_programa_pai::text as co_programa_pai + from {{ source("sisbolsas", "tb_programa") }} + ) + +select * +from sisbolsas_tb_programa diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_selecao.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_selecao.sql new file mode 100644 index 00000000..b655087d --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_selecao.sql @@ -0,0 +1,35 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_selecao as ( + select + co_selecao::text as co_selecao, + co_chamada_publica::text as co_chamada_publica, + co_modalidade::text as co_modalidade, + co_estado::text as co_estado, + co_area_formacao::text as co_area_formacao, + co_nivel_escolaridade::text as co_nivel_escolaridade, + tp_atuacao::text as tp_atuacao, + st_nivel_escolaridade::text as st_nivel_escolaridade, + qt_duracao::text as qt_duracao, + {{ safe_numeric('vl_total') }} as vl_total, + qt_bolsa::text as qt_bolsa, + ds_area_especializacao::text as ds_area_especializacao, + ds_outra_formacao::text as ds_outra_formacao, + {{ safe_boolean('in_anexo_projeto') }} as in_anexo_projeto, + {{ safe_boolean('in_anexo_idioma') }} as in_anexo_idioma, + {{ safe_boolean('in_anexo_historico_escolar') }} as in_anexo_historico_escolar, + dt_criacao::text as dt_criacao, + co_usuario::text as co_usuario, + co_cidade::text as co_cidade, + nu_passo_avaliacao::text as nu_passo_avaliacao, + st_avaliacao::text as st_avaliacao, + {{ safe_boolean('in_entrevista') }} as in_entrevista, + nu_selecao::text as nu_selecao, + {{ safe_boolean('in_anexo_outro') }} as in_anexo_outro, + ds_anexo_outro::text as ds_anexo_outro + from {{ source("sisbolsas", "tb_selecao") }} + ) + +select * +from sisbolsas_tb_selecao diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_situacao_chamada.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_situacao_chamada.sql new file mode 100644 index 00000000..310eb587 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_situacao_chamada.sql @@ -0,0 +1,12 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_situacao_chamada as ( + select + co_situacao_chamada::text as co_situacao_chamada, + ds_situacao_chamada::text as ds_situacao_chamada + from {{ source("sisbolsas", "tb_situacao_chamada") }} + ) + +select * +from sisbolsas_tb_situacao_chamada diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_situacao_concessao.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_situacao_concessao.sql new file mode 100644 index 00000000..adedeb23 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_situacao_concessao.sql @@ -0,0 +1,12 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_situacao_concessao as ( + select + co_situacao_concessao::text as co_situacao_concessao, + ds_situacao_concessao::text as ds_situacao_concessao + from {{ source("sisbolsas", "tb_situacao_concessao") }} + ) + +select * +from sisbolsas_tb_situacao_concessao diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_unidade.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_unidade.sql new file mode 100644 index 00000000..ae0ea4bd --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_unidade.sql @@ -0,0 +1,15 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_unidade as ( + select + co_unidade::text as co_unidade, + ds_unidade::text as ds_unidade, + co_estado::text as co_estado, + ds_sigla::text as ds_sigla, + co_diretoria::text as co_diretoria + from {{ source("sisbolsas", "tb_unidade") }} + ) + +select * +from sisbolsas_tb_unidade diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_usuario.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_usuario.sql new file mode 100644 index 00000000..02798275 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sisbolsas_tb_usuario.sql @@ -0,0 +1,22 @@ +{{ config(materialized="table") }} + +with + sisbolsas_tb_usuario as ( + select + co_usuario::text as co_usuario, + ds_nome::text as ds_nome, + ds_login::text as ds_login, + co_perfil::text as co_perfil, + ds_email::text as ds_email, + ds_senha::text as ds_senha, + dt_criacao::text as dt_criacao, + co_tipo_login::text as co_tipo_login, + st_usuario::text as st_usuario, + co_anexo::text as co_anexo, + ds_token_redefinir_senha::text as ds_token_redefinir_senha, + dt_token_senha::text as dt_token_senha + from {{ source("sisbolsas", "tb_usuario") }} + ) + +select * +from sisbolsas_tb_usuario diff --git a/airflow_lappis/dags/dbt/ipea/models/sources.yml b/airflow_lappis/dags/dbt/ipea/models/sources.yml index aa4c3af6..2aa9dded 100644 --- a/airflow_lappis/dags/dbt/ipea/models/sources.yml +++ b/airflow_lappis/dags/dbt/ipea/models/sources.yml @@ -74,3 +74,49 @@ sources: schema: senado_federal tables: - name: senadores + + - name: ipea_pro + schema: ipea_pro + tables: + - name: bolsistas + - name: coordenacoes + - name: custosemprojetos + - name: diretorias + - name: fontesreceitas + - name: grupoentidade + - name: insumofinanceiro + - name: itemfontereceitas + - name: itenscustos + - name: projetos + - name: registrofinanceiroemprojetos + - name: servidorespublicos + - name: statusprojetos + + - name: sisbolsas + schema: sisbolsas + tables: + - name: tb_area_formacao + - name: tb_bolsa + - name: tb_bolsa_coordenador + - name: tb_bolsa_fonterecurso + - name: tb_bolsista + - name: tb_chamada_publica + - name: tb_chapubli_fontfina + - name: tb_chapubli_unidade + - name: tb_coordenador + - name: tb_dado_formacao + - name: tb_dado_pessoal + - name: tb_dado_profissional + - name: tb_estado + - name: tb_folha_bolsista + - name: tb_folha_pagamento + - name: tb_fonte_financeira + - name: tb_modalidade + - name: tb_nivel_escolaridade + - name: tb_processo_seletivo + - name: tb_programa + - name: tb_selecao + - name: tb_situacao_chamada + - name: tb_situacao_concessao + - name: tb_unidade + - name: tb_usuario From c1bf7e05b9626eff21afc377854528ff76f8f343 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Sun, 26 Apr 2026 21:47:46 -0300 Subject: [PATCH 276/317] chore: remove dag vazia de mandatos --- .../dados_abertos/mandatos_parlamentares_ingest_dag.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 airflow_lappis/dags/data_ingest/dados_abertos/mandatos_parlamentares_ingest_dag.py diff --git a/airflow_lappis/dags/data_ingest/dados_abertos/mandatos_parlamentares_ingest_dag.py b/airflow_lappis/dags/data_ingest/dados_abertos/mandatos_parlamentares_ingest_dag.py deleted file mode 100644 index e69de29b..00000000 From 2c6f9f1d8ee79e20a5cb0fa1fc6dc02a76b2629f Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Sun, 26 Apr 2026 21:52:39 -0300 Subject: [PATCH 277/317] feat: camada bronze datas legislaturas --- .../dados_abertos_dbt/bronze/legislaturas.sql | 15 +++++ .../dados_abertos_dbt/bronze/schema.yml | 58 +++++++++++++++++++ .../dags/dbt/mir/models/sources.yml | 1 + 3 files changed, 74 insertions(+) create mode 100644 airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/legislaturas.sql diff --git a/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/legislaturas.sql b/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/legislaturas.sql new file mode 100644 index 00000000..5e316a49 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/legislaturas.sql @@ -0,0 +1,15 @@ +{{ config(materialized="table") }} + +with + legislaturas_raw as ( + select + id::integer as id, + data_inicio::date as data_inicio, + data_fim::date as data_fim, + data_eleicao::date as data_eleicao, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("senado_federal", "legislaturas") }} + ) + +select * +from legislaturas_raw diff --git a/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/schema.yml b/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/schema.yml index 13cbc8ba..27d841e4 100644 --- a/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/schema.yml +++ b/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/schema.yml @@ -194,3 +194,61 @@ models: nome_tabela: 'senadores.senadores' nome_coluna: 'dt_ingest' tipo_esperado: 'timestamp with time zone' + + - name: legislaturas + description: > + Tabela com informações de legislaturas da Câmara dos Deputados, incluindo + identificador, período de vigência e data de eleição. + Esta tabela representa a camada bronze do pipeline, com padronização de tipos + e ajuste de fuso horário na data de ingestão. + Os dados são provenientes da fonte camara_deputados.legislaturas (camada raw) e + passam por conversão explícita de tipos para garantir consistência e rastreabilidade. + meta: + tags: + - bronze + columns: + - name: id + description: > + Identificador único da legislatura na API da Câmara dos Deputados. + + - name: data_inicio + description: > + Data de início da legislatura. + + - name: data_fim + description: > + Data de término da legislatura. + + - name: data_eleicao + description: > + Data da eleição associada à legislatura. + + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) em que os dados foram ingeridos da fonte original para a camada raw. + + tests: + - verificacao_tipagem: + nome_tabela: 'legislaturas.legislaturas' + nome_coluna: 'id' + tipo_esperado: 'integer' + + - verificacao_tipagem: + nome_tabela: 'legislaturas.legislaturas' + nome_coluna: 'data_inicio' + tipo_esperado: 'date' + + - verificacao_tipagem: + nome_tabela: 'legislaturas.legislaturas' + nome_coluna: 'data_fim' + tipo_esperado: 'date' + + - verificacao_tipagem: + nome_tabela: 'legislaturas.legislaturas' + nome_coluna: 'data_eleicao' + tipo_esperado: 'date' + + - verificacao_tipagem: + nome_tabela: 'legislaturas.legislaturas' + nome_coluna: 'dt_ingest' + tipo_esperado: 'timestamp with time zone' diff --git a/airflow_lappis/dags/dbt/mir/models/sources.yml b/airflow_lappis/dags/dbt/mir/models/sources.yml index 491a0b8d..5827fd7a 100644 --- a/airflow_lappis/dags/dbt/mir/models/sources.yml +++ b/airflow_lappis/dags/dbt/mir/models/sources.yml @@ -26,6 +26,7 @@ sources: schema: senado_federal tables: - name: senadores + - name: legislaturas - name: transfere_gov schema: transfere_gov From fbd7bb909152df80c7179681fe118eb3d6db6567 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Mon, 27 Apr 2026 03:46:58 -0300 Subject: [PATCH 278/317] =?UTF-8?q?feat:=20prote=C3=A7=C3=A3o=20de=20dup?= =?UTF-8?q?=20parlamentares=20controle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../parlamentares_controle_historico_dag.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/airflow_lappis/dags/data_ingest/dados_abertos/parlamentares_controle_historico_dag.py b/airflow_lappis/dags/data_ingest/dados_abertos/parlamentares_controle_historico_dag.py index 85c0cbcd..4e6c29d2 100644 --- a/airflow_lappis/dags/data_ingest/dados_abertos/parlamentares_controle_historico_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_abertos/parlamentares_controle_historico_dag.py @@ -419,6 +419,20 @@ def extrair_historico(candidatos: list[dict[str, str]]) -> None: ) if historico_camara: + # Proteção contra duplicidade: limpa os registros anteriores antes de inserir o novo batch da API + camara_ids = tuple(set(item["parlamentar_id"] for item in historico_camara)) + with psycopg2.connect(conn_str) as conn: + with conn.cursor() as cursor: + cursor.execute( + "SELECT EXISTS(SELECT 1 FROM information_schema.tables WHERE table_schema=%s AND table_name=%s)", + ("camara_deputados", "deputados_historico") + ) + if cursor.fetchone()[0]: + cursor.execute( + "DELETE FROM camara_deputados.deputados_historico WHERE parlamentar_id IN %s", + (camara_ids,) + ) + db.insert_data( historico_camara, table_name="deputados_historico", @@ -426,6 +440,20 @@ def extrair_historico(candidatos: list[dict[str, str]]) -> None: ) if historico_senado: + # Proteção contra duplicidade: limpa os registros anteriores antes de inserir o novo batch da API + senado_ids = tuple(set(item["parlamentar_id"] for item in historico_senado)) + with psycopg2.connect(conn_str) as conn: + with conn.cursor() as cursor: + cursor.execute( + "SELECT EXISTS(SELECT 1 FROM information_schema.tables WHERE table_schema=%s AND table_name=%s)", + ("senado_federal", "senadores_historico") + ) + if cursor.fetchone()[0]: + cursor.execute( + "DELETE FROM senado_federal.senadores_historico WHERE parlamentar_id IN %s", + (senado_ids,) + ) + db.insert_data( historico_senado, table_name="senadores_historico", From 8536fc68035d1b8dc9ce69c4910342c494eceb90 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Mon, 27 Apr 2026 03:56:16 -0300 Subject: [PATCH 279/317] =?UTF-8?q?chore:=20modulariza=C3=A7=C3=A3o=20e=20?= =?UTF-8?q?comentarios=20em=20parlamentares?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../parlamentares_controle_historico_dag.py | 242 +++++++----------- 1 file changed, 92 insertions(+), 150 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/dados_abertos/parlamentares_controle_historico_dag.py b/airflow_lappis/dags/data_ingest/dados_abertos/parlamentares_controle_historico_dag.py index 4e6c29d2..9ba44929 100644 --- a/airflow_lappis/dags/data_ingest/dados_abertos/parlamentares_controle_historico_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_abertos/parlamentares_controle_historico_dag.py @@ -11,10 +11,72 @@ from postgres_helpers import get_postgres_conn from schedule_loader import get_dynamic_schedule - CONTROLE_TABLE = "parlamentares_controle" CONTROLE_SCHEMA = "dados_abertos" +# Helpers + +def _table_exists(cursor, schema: str, table: str) -> bool: + cursor.execute( + """ + SELECT EXISTS ( + SELECT 1 + FROM information_schema.tables + WHERE table_schema = %s + AND table_name = %s + ) + """, + (schema, table), + ) + return bool(cursor.fetchone()[0]) + +def _table_has_column(cursor, schema: str, table: str, column: str) -> bool: + cursor.execute( + """ + SELECT EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_schema = %s + AND table_name = %s + AND column_name = %s + ) + """, + (schema, table, column), + ) + return bool(cursor.fetchone()[0]) + +def _fetch_historico_ids(cursor, schema: str, table: str) -> set[int]: + if not _table_exists(cursor, schema, table): + return set() + + cursor.execute( + f""" + SELECT DISTINCT CAST(id::text AS BIGINT) + FROM {schema}.{table} + WHERE id IS NOT NULL + AND id::text ~ '^[0-9]+$' + """ + ) + return {int(row[0]) for row in cursor.fetchall()} + +def _clean_existing_historico(conn_str: str, schema: str, table: str, records: list[dict]) -> None: + """Remove o histórico antigo para evitar duplicação antes do insert em lote.""" + if not records: + return + + ids = tuple(set(item["parlamentar_id"] for item in records if "parlamentar_id" in item)) + if not ids: + return + + with psycopg2.connect(conn_str) as conn: + with conn.cursor() as cursor: + if _table_exists(cursor, schema, table): + cursor.execute( + f"DELETE FROM {schema}.{table} WHERE parlamentar_id IN %s", + (ids,) + ) + +# --- @dag( schedule_interval=get_dynamic_schedule("parlamentares_controle_historico_dag"), @@ -28,15 +90,12 @@ tags=["MIR", "dados_abertos", "parlamentares", "deputados", "senadores", "historico"], ) def parlamentares_controle_historico_dag() -> None: - """Sincroniza parlamentares atuais e controla extração de histórico por estado.""" + """Sincroniza parlamentares atuais e controla extração de histórico por ciclo temporal.""" @task def sync_atuais() -> dict[str, list[int]]: """Task 1: Busca parlamentares atuais da Câmara e do Senado.""" - logging.info( - "[parlamentares_controle_historico_dag.py] " - "Iniciando sync de parlamentares atuais" - ) + logging.info("[parlamentares_controle_historico_dag.py] Iniciando sync de parlamentares atuais") cliente_deputados = ClienteDeputados() cliente_senadores = ClienteSenadores() @@ -65,8 +124,7 @@ def sync_atuais() -> dict[str, list[int]]: int(item.get("IdentificacaoParlamentar", {}).get("CodigoParlamentar")) for item in senadores if isinstance(item, dict) - and item.get("IdentificacaoParlamentar", {}).get("CodigoParlamentar") - is not None + and item.get("IdentificacaoParlamentar", {}).get("CodigoParlamentar") is not None } payload = { @@ -75,14 +133,14 @@ def sync_atuais() -> dict[str, list[int]]: } logging.info( - "[parlamentares_controle_historico_dag.py] Sync concluido: " + f"[parlamentares_controle_historico_dag.py] Sync concluido: " f"camara={len(payload['camara'])}, senado={len(payload['senado'])}" ) return payload @task def state_logic(parlamentares_atuais: dict[str, list[int]]) -> list[dict[str, str]]: - """Task 2: Mantém a tabela de controle com estados de ciclo de vida.""" + """Task 2: Mantém a tabela de controle com estados de atividade.""" conn_str = get_postgres_conn("postgres_mir") create_table_sql = f""" @@ -101,58 +159,6 @@ def state_logic(parlamentares_atuais: dict[str, list[int]]) -> list[dict[str, st now = datetime.now() - def _table_exists( - cursor: psycopg2.extensions.cursor, schema: str, table: str - ) -> bool: - cursor.execute( - """ - SELECT EXISTS ( - SELECT 1 - FROM information_schema.tables - WHERE table_schema = %s - AND table_name = %s - ) - """, - (schema, table), - ) - return bool(cursor.fetchone()[0]) - - def _fetch_historico_ids( - cursor: psycopg2.extensions.cursor, schema: str, table: str - ) -> set[int]: - if not _table_exists(cursor, schema, table): - return set() - - cursor.execute( - f""" - SELECT DISTINCT CAST(id::text AS BIGINT) - FROM {schema}.{table} - WHERE id IS NOT NULL - AND id::text ~ '^[0-9]+$' - """ - ) - return {int(row[0]) for row in cursor.fetchall()} - - def _table_has_column( - cursor: psycopg2.extensions.cursor, - schema: str, - table: str, - column: str, - ) -> bool: - cursor.execute( - """ - SELECT EXISTS ( - SELECT 1 - FROM information_schema.columns - WHERE table_schema = %s - AND table_name = %s - AND column_name = %s - ) - """, - (schema, table, column), - ) - return bool(cursor.fetchone()[0]) - with psycopg2.connect(conn_str) as conn: with conn.cursor() as cursor: cursor.execute(create_table_sql) @@ -161,12 +167,8 @@ def _table_has_column( controle_vazio = cursor.fetchone()[0] == 0 if controle_vazio: - historico_camara_ids = _fetch_historico_ids( - cursor, "camara_deputados", "deputados" - ) - historico_senado_ids = _fetch_historico_ids( - cursor, "senado_federal", "senadores" - ) + historico_camara_ids = _fetch_historico_ids(cursor, "camara_deputados", "deputados") + historico_senado_ids = _fetch_historico_ids(cursor, "senado_federal", "senadores") for fonte, ids_historicos in ( ("camara", historico_camara_ids), @@ -194,14 +196,7 @@ def _table_has_column( cursor, f""" INSERT INTO {CONTROLE_SCHEMA}.{CONTROLE_TABLE} - ( - fonte, - parlamentar_id, - status, - first_seen_at, - last_seen_at, - updated_at - ) + (fonte, parlamentar_id, status, first_seen_at, last_seen_at, updated_at) VALUES %s ON CONFLICT (fonte, parlamentar_id) DO UPDATE SET @@ -212,10 +207,8 @@ def _table_has_column( ) logging.info( - "[parlamentares_controle_historico_dag.py] " - "Bootstrap realizado " - f"para fonte={fonte}: universo={len(universo)}, " - f"atuais={len(ids_atuais)}" + f"[parlamentares_controle_historico_dag.py] Bootstrap realizado para " + f"fonte={fonte}: universo={len(universo)}, atuais={len(ids_atuais)}" ) for fonte in ("camara", "senado"): @@ -230,14 +223,7 @@ def _table_has_column( cursor, f""" INSERT INTO {CONTROLE_SCHEMA}.{CONTROLE_TABLE} - ( - fonte, - parlamentar_id, - status, - first_seen_at, - last_seen_at, - updated_at - ) + (fonte, parlamentar_id, status, first_seen_at, last_seen_at, updated_at) VALUES %s ON CONFLICT (fonte, parlamentar_id) DO UPDATE SET @@ -261,20 +247,13 @@ def _table_has_column( ) else: logging.warning( - "[parlamentares_controle_historico_dag.py] " - "Snapshot de atuais vazio para fonte=" - f"{fonte}. Fechamento ignorado nesta execucao." + f"[parlamentares_controle_historico_dag.py] Snapshot de atuais vazio para " + f"fonte={fonte}. Fechamento ignorado nesta execucao." ) - # Se já existe histórico carregado para o parlamentar, evita recarga - # inicial desnecessária marcando last_historico_at para os sem valor. - if _table_exists( - cursor, "camara_deputados", "deputados_historico" - ) and _table_has_column( - cursor, - "camara_deputados", - "deputados_historico", - "parlamentar_id", + # Evita recarga inicial desnecessária em parlamentares já contidos na bronze nativa + if _table_exists(cursor, "camara_deputados", "deputados_historico") and _table_has_column( + cursor, "camara_deputados", "deputados_historico", "parlamentar_id" ): cursor.execute( f""" @@ -287,20 +266,14 @@ def _table_has_column( SELECT 1 FROM camara_deputados.deputados_historico h WHERE h.parlamentar_id::text ~ '^[0-9]+$' - AND CAST(h.parlamentar_id::text AS BIGINT) - = c.parlamentar_id + AND CAST(h.parlamentar_id::text AS BIGINT) = c.parlamentar_id ) """, (now, now), ) - if _table_exists( - cursor, "senado_federal", "senadores_historico" - ) and _table_has_column( - cursor, - "senado_federal", - "senadores_historico", - "parlamentar_id", + if _table_exists(cursor, "senado_federal", "senadores_historico") and _table_has_column( + cursor, "senado_federal", "senadores_historico", "parlamentar_id" ): cursor.execute( f""" @@ -313,8 +286,7 @@ def _table_has_column( SELECT 1 FROM senado_federal.senadores_historico h WHERE h.parlamentar_id::text ~ '^[0-9]+$' - AND CAST(h.parlamentar_id::text AS BIGINT) - = c.parlamentar_id + AND CAST(h.parlamentar_id::text AS BIGINT) = c.parlamentar_id ) """, (now, now), @@ -348,19 +320,16 @@ def _table_has_column( ] logging.info( - "[parlamentares_controle_historico_dag.py] State logic concluido. " + f"[parlamentares_controle_historico_dag.py] State logic concluido. " f"Parlamentares elegiveis para historico: {len(candidatos)}" ) return candidatos @task def extrair_historico(candidatos: list[dict[str, str]]) -> None: - """Task 3: Extrai histórico conforme estado e atualiza ciclo de vida.""" + """Task 3: Extrai histórico da fonte oficial e injeta na base.""" if not candidatos: - logging.info( - "[parlamentares_controle_historico_dag.py] " - "Nenhum parlamentar elegivel para historico" - ) + logging.info("[parlamentares_controle_historico_dag.py] Nenhum parlamentar elegivel para historico") return conn_str = get_postgres_conn("postgres_mir") @@ -404,9 +373,8 @@ def extrair_historico(candidatos: list[dict[str, str]]) -> None: if not extracao_ok: logging.warning( - "[parlamentares_controle_historico_dag.py] Sem confirmação de " - f"extração para fonte={fonte}, parlamentar_id={parlamentar_id}. " - "Status mantido." + f"[parlamentares_controle_historico_dag.py] Sem confirmação de " + f"extração para fonte={fonte}, parlamentar_id={parlamentar_id}. Status mantido." ) continue @@ -414,25 +382,12 @@ def extrair_historico(candidatos: list[dict[str, str]]) -> None: status_updates.append((novo_status, fonte, parlamentar_id)) except Exception as e: logging.error( - "[parlamentares_controle_historico_dag.py] Erro ao extrair historico " + f"[parlamentares_controle_historico_dag.py] Erro ao extrair historico " f"fonte={fonte}, parlamentar_id={parlamentar_id}: {e}" ) if historico_camara: - # Proteção contra duplicidade: limpa os registros anteriores antes de inserir o novo batch da API - camara_ids = tuple(set(item["parlamentar_id"] for item in historico_camara)) - with psycopg2.connect(conn_str) as conn: - with conn.cursor() as cursor: - cursor.execute( - "SELECT EXISTS(SELECT 1 FROM information_schema.tables WHERE table_schema=%s AND table_name=%s)", - ("camara_deputados", "deputados_historico") - ) - if cursor.fetchone()[0]: - cursor.execute( - "DELETE FROM camara_deputados.deputados_historico WHERE parlamentar_id IN %s", - (camara_ids,) - ) - + _clean_existing_historico(conn_str, "camara_deputados", "deputados_historico", historico_camara) db.insert_data( historico_camara, table_name="deputados_historico", @@ -440,20 +395,7 @@ def extrair_historico(candidatos: list[dict[str, str]]) -> None: ) if historico_senado: - # Proteção contra duplicidade: limpa os registros anteriores antes de inserir o novo batch da API - senado_ids = tuple(set(item["parlamentar_id"] for item in historico_senado)) - with psycopg2.connect(conn_str) as conn: - with conn.cursor() as cursor: - cursor.execute( - "SELECT EXISTS(SELECT 1 FROM information_schema.tables WHERE table_schema=%s AND table_name=%s)", - ("senado_federal", "senadores_historico") - ) - if cursor.fetchone()[0]: - cursor.execute( - "DELETE FROM senado_federal.senadores_historico WHERE parlamentar_id IN %s", - (senado_ids,) - ) - + _clean_existing_historico(conn_str, "senado_federal", "senadores_historico", historico_senado) db.insert_data( historico_senado, table_name="senadores_historico", @@ -480,7 +422,7 @@ def extrair_historico(candidatos: list[dict[str, str]]) -> None: ) logging.info( - "[parlamentares_controle_historico_dag.py] Extração concluida. " + f"[parlamentares_controle_historico_dag.py] Extração concluida. " f"Historico camara={len(historico_camara)}, " f"historico senado={len(historico_senado)}, " f"status atualizados={len(status_updates)}" From a3b62e50f4eede9c123caa5da81cf5e3464dac78 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Mon, 27 Apr 2026 04:16:21 -0300 Subject: [PATCH 280/317] feat: bronze deputados historicos com tratamento --- .../bronze/deputados_historico.sql | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/deputados_historico.sql diff --git a/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/deputados_historico.sql b/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/deputados_historico.sql new file mode 100644 index 00000000..d826d702 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/deputados_historico.sql @@ -0,0 +1,72 @@ +{{ config(materialized="table") }} + +with + deputados_raw as ( + select + id::integer as id, + nome::text as nome, + siglapartido::text as sigla_partido, + uripartido::text as uri_partido, + siglauf::text as sigla_uf, + idlegislatura::integer as id_legislatura, + datahora::timestamptz as data_evento, + trim(situacao)::text as situacao, + condicaoeleitoral::text as condicao_eleitoral, + parlamentar_id::integer as parlamentar_id, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("camara_deputados", "deputados_historico") }} + where situacao is not null + and situacao != '' + ), + + -- Legislatura Atual para evitar vazamentos de estado ativo em mandatos extintos + meta_legislatura as ( + select max(id_legislatura) as max_id_leg from deputados_raw + ), + + legislaturas_dim as ( + select + id, + data_fim::timestamptz as data_fim_legislatura + from {{ ref("legislaturas") }} + ), + + calculo_periodos as ( + select + dr.*, + lead(dr.data_evento) over ( + partition by dr.id + order by dr.data_evento asc + ) as proximo_evento_data, + ml.max_id_leg, + ld.data_fim_legislatura + from deputados_raw dr + cross join meta_legislatura ml + left join legislaturas_dim ld + on dr.id_legislatura = ld.id + ) + +select + id, + nome, + sigla_partido, + id_legislatura, + situacao, + data_evento as data_filiacao, + case + -- Caso 1: Existe um próximo registro (segue a cronologia normal) + when proximo_evento_data is not null then proximo_evento_data + + -- Caso 2: É o último registro, mas a situação é de encerramento explícito (Vacância/Fim de Mandato/Falecimento) + when situacao in ('Vacância', 'Fim de Mandato', 'Falecimento') then data_evento + + -- Caso 3: É o último registro, restando qualquer outra situação, mas de uma LEGISLATURA ANTIGA + when id_legislatura < max_id_leg then coalesce(data_fim_legislatura, data_evento) + + -- Caso 4: É o último registro, restando qualquer outra situação, na legislatura ATUAL + when id_legislatura = max_id_leg then null + + else null + end as data_desfiliacao, + dt_ingest +from calculo_periodos \ No newline at end of file From 583a828c8d720a1f67431088f661e5e4091593c5 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Mon, 27 Apr 2026 04:17:59 -0300 Subject: [PATCH 281/317] feat: bronze senadores historico --- .../bronze/senadores_historico.sql | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/senadores_historico.sql diff --git a/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/senadores_historico.sql b/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/senadores_historico.sql new file mode 100644 index 00000000..09917699 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/senadores_historico.sql @@ -0,0 +1,39 @@ +{{ config(materialized="table") }} + +with + senadores_raw as ( + select + parlamentar_id::integer as parlamentar_id, + partido__codigopartido::text as codigo_partido, + partido__siglapartido::text as sigla_partido, + partido__nomepartido::text as nome_partido, + parlamentar__nome:: text as nome, + case + when lower(trim(datafiliacao::text)) in ('', 'nan', 'null') then null + else datafiliacao::timestamptz + end as data_filiacao, + case + when lower(trim(datadesfiliacao::text)) in ('', 'nan', 'null') then null + else datadesfiliacao::timestamptz + end as data_desfiliação, + case + when trim(anofiliacao::text) ~ '^[0-9]{4}$' then anofiliacao::integer + else null + end as ano_filiacao, + case + when trim(anodesfiliacao::text) ~ '^[0-9]{4}$' then anodesfiliacao::integer + else null + end as ano_desfiliação, + fonte:: text as fonte, + (dt_ingest || '-03:00')::timestamptz as dt_ingest + from {{ source("senado_federal", "senadores_historico") }} + ), + + senadores_filtrados as ( + select * + from senadores_raw + where (data_filiacao is not null and data_filiacao >= '1995-01-01'::timestamptz) -- data confiável + ) + +select * +from senadores_filtrados From 8bffedfe06cb40e937f3bda095a782dc6957b0aa Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Mon, 27 Apr 2026 04:31:11 -0300 Subject: [PATCH 282/317] feat: cruzamento senadores deputados historico --- .../dags/dbt/mir/macros/name_formater.sql | 3 + .../silver/parlamentares_historico.sql | 133 ++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 airflow_lappis/dags/dbt/mir/macros/name_formater.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/silver/parlamentares_historico.sql diff --git a/airflow_lappis/dags/dbt/mir/macros/name_formater.sql b/airflow_lappis/dags/dbt/mir/macros/name_formater.sql new file mode 100644 index 00000000..e736bcb3 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/macros/name_formater.sql @@ -0,0 +1,3 @@ +{% macro name_formater(column_name) %} + TRIM(TRANSLATE(UPPER({{ column_name }}), 'ÁÀÂÃÄÅÉÈÊËÍÌÎÏÓÒÔÕÖÚÙÛÜÇÑ', 'AAAAAAEEEEIIIIOOOOOUUUUCN')) +{% endmacro %} diff --git a/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/silver/parlamentares_historico.sql b/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/silver/parlamentares_historico.sql new file mode 100644 index 00000000..fa7590da --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/silver/parlamentares_historico.sql @@ -0,0 +1,133 @@ +{{ config(materialized='table') }} + +WITH +bronze_deputados_historico AS ( + SELECT * FROM {{ ref('deputados_historico') }} +), + +bronze_deputados AS ( + SELECT * FROM {{ ref('deputados') }} +), + +deputados_lookup AS ( + SELECT DISTINCT ON (id) + id, + nome, + siglauf, + urlfoto, + email, + dt_ingest + FROM bronze_deputados + ORDER BY id, dt_ingest DESC +), + +bronze_senadores_historico AS ( + SELECT * FROM {{ ref('senadores_historico') }} +), + +bronze_senadores AS ( + SELECT * FROM {{ ref('senadores') }} +), + +senadores_lookup AS ( + SELECT DISTINCT ON (id) + id, + nome_parlamentar, + uf, + url_foto, + email, + dt_ingest + FROM bronze_senadores + ORDER BY id, dt_ingest DESC +), + +sigla_map AS ( + SELECT + TRIM(UPPER(sigla_origem)) AS sigla_origem, + MAX(TRIM(UPPER(sigla_canonica))) AS sigla_canonica + FROM {{ ref('partidos_map') }} + GROUP BY 1 +), + +parlamentares_unificados AS ( + SELECT + dh.id AS id_parlamentar, + {{ name_formater("COALESCE(NULLIF(dh.nome, ''), d.nome)") }} AS chave_join_nome, + COALESCE(NULLIF(dh.nome, ''), d.nome) AS nome_parlamentar, + 'Deputado' AS cargo_parlamentar, + dh.sigla_partido AS sigla_partido, + d.siglauf AS uf_parlamentar, + d.urlfoto AS url_foto, + d.email AS email, + dh.data_filiacao, + dh.data_desfiliacao, + dh.id_legislatura, + dh.situacao, + NULL::text AS fonte, + dh.dt_ingest + FROM bronze_deputados_historico dh + LEFT JOIN deputados_lookup d + ON dh.id = d.id + + UNION ALL + + SELECT + sh.parlamentar_id AS id_parlamentar, + {{ name_formater("COALESCE(NULLIF(sh.nome, ''), s.nome_parlamentar)") }} AS chave_join_nome, + COALESCE(NULLIF(sh.nome, ''), s.nome_parlamentar) AS nome_parlamentar, + 'Senador' AS cargo_parlamentar, + sh.sigla_partido AS sigla_partido, + s.uf AS uf_parlamentar, + s.url_foto AS url_foto, + s.email AS email, + sh.data_filiacao, + sh.data_desfiliação AS data_desfiliacao, + NULL::integer AS id_legislatura, + NULL::text AS situacao, + sh.fonte, + sh.dt_ingest + FROM bronze_senadores_historico sh + LEFT JOIN senadores_lookup s + ON sh.parlamentar_id = s.id +), + +parlamentares_padronizados AS ( + SELECT + p.*, + COALESCE(m.sigla_canonica, p.sigla_partido) AS sigla_partido_padronizada + FROM parlamentares_unificados p + LEFT JOIN sigla_map m + ON TRIM(UPPER(p.sigla_partido)) = m.sigla_origem +), + +partidos_logo AS ( + SELECT + TRIM(UPPER(sigla)) AS chave_join_sigla_partido, + MAX(logo_url) AS logo_url + FROM {{ ref('partidos_logo') }} + GROUP BY 1 +) + +SELECT + p.id_parlamentar, + p.chave_join_nome, + p.nome_parlamentar, + p.cargo_parlamentar, + + p.sigla_partido_padronizada AS sigla_partido, + + p.uf_parlamentar, + p.url_foto, + p.email, + p.data_filiacao, + p.data_desfiliacao, + p.id_legislatura, + p.situacao, + p.fonte, + p.dt_ingest, + pl.logo_url AS url_logo_partido + +FROM parlamentares_padronizados p + +LEFT JOIN partidos_logo pl + ON TRIM(UPPER(p.sigla_partido_padronizada)) = pl.chave_join_sigla_partido \ No newline at end of file From 1a18de69e3be08b7bc0e2149b38dd38759759440 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Mon, 27 Apr 2026 04:32:18 -0300 Subject: [PATCH 283/317] feat: cruzamento parlamentares historico emendas --- .../emendas_dbt/silver/emendas_partidos.sql | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 airflow_lappis/dags/dbt/mir/models/emendas_dbt/silver/emendas_partidos.sql diff --git a/airflow_lappis/dags/dbt/mir/models/emendas_dbt/silver/emendas_partidos.sql b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/silver/emendas_partidos.sql new file mode 100644 index 00000000..1a57629b --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/silver/emendas_partidos.sql @@ -0,0 +1,151 @@ +{{ config(materialized='table') }} + +WITH tg_emendas AS ( + SELECT * + FROM {{ ref('tg_emendas') }} +), + +parlamentares_hist AS ( + SELECT * + FROM {{ ref('parlamentares_historico') }} +), + +tg_emendas_tratado AS ( + SELECT + *, + {{ name_formater("autor_emendas_orcamento_nome") }} AS chave_join_nome, + ROW_NUMBER() OVER () as emenda_id + FROM tg_emendas +), + +cruzamento_bruto AS ( + SELECT + e.emissao_mes, + e.emissao_dia, + e.programa_governo AS codigo_programa, + e.programa_governo_descricao AS programa, + e.acao_governo AS codigo_acao_ajustada, + e.acao_governo_descricao AS acao_ajustada, + e.autor_emendas_orcamento_descricao, + e.uf_pt AS uf, + e.uf_pt_descricao AS uf_descricao, + e.municipio_pt AS municipio, + 'Brasil' AS pais, + e.ne_ccor, + e.ne_num_processo, + e.ne_info_complementar, + e.ne_ccor_descricao, + e.doc_observacao, + e.grupo_despesa AS codigo_gnd, + e.grupo_despesa_descricao AS gnd, + e.natureza_despesa, + e.natureza_despesa_descricao, + e.modalidade_aplicacao AS codigo_modalidade, + e.modalidade_aplicacao_descricao AS modalidade, + e.ne_ccor_favorecido, + e.ne_ccor_favorecido_descricao, + e.ne_ccor_ano_emissao, + e.ptres, + e.item_informacao, + e.item_informacao_descricao, + e.despesas_empenhadas, + e.despesas_liquidadas, + e.despesas_pagas, + e.autor_emendas_orcamento_nome, + + e.emenda_id, + + p.id_parlamentar as id_autor, + p.cargo_parlamentar as cargo_autor, + p.nome_parlamentar as autor, + p.sigla_partido as partido, + p.uf_parlamentar as uf_autor, + p.url_foto as url_foto_autor, + p.email as email_autor, + p.url_logo_partido as url_foto_partido, + + e.dt_ingest, + + -- Prioridade de cruzamento + CASE + WHEN e.emissao_dia >= p.data_filiacao::date + AND e.emissao_dia <= COALESCE(p.data_desfiliacao::date, CURRENT_DATE) + THEN 1 + -- Se achou nome, mas a data não bateu + WHEN p.id_parlamentar IS NOT NULL + THEN 2 + -- Nomes que nem existem + ELSE 3 + END as prioridade_match, + + -- Distância de fallback para quando não tivermos batido o range + LEAST( + ABS(EXTRACT(EPOCH FROM (e.emissao_dia::timestamptz - p.data_filiacao))), + ABS(EXTRACT(EPOCH FROM (e.emissao_dia::timestamptz - COALESCE(p.data_desfiliacao, CURRENT_TIMESTAMP)))) + ) AS distancia_tempo + + FROM tg_emendas_tratado e + LEFT JOIN parlamentares_hist p + ON e.chave_join_nome = p.chave_join_nome +), + +deduplicado AS ( + SELECT * + FROM ( + SELECT *, + ROW_NUMBER() OVER ( + PARTITION BY emenda_id + ORDER BY + prioridade_match ASC, + distancia_tempo ASC + ) as rn + FROM cruzamento_bruto + ) sub + WHERE rn = 1 +) + +SELECT + emissao_mes, + emissao_dia, + codigo_programa, + programa, + codigo_acao_ajustada, + acao_ajustada, + autor_emendas_orcamento_descricao, + autor_emendas_orcamento_nome, + uf, + uf_descricao, + municipio, + pais, + ne_ccor, + ne_num_processo, + ne_info_complementar, + ne_ccor_descricao, + doc_observacao, + codigo_gnd, + gnd, + natureza_despesa, + natureza_despesa_descricao, + codigo_modalidade, + modalidade, + ne_ccor_favorecido, + ne_ccor_favorecido_descricao, + ne_ccor_ano_emissao, + ptres, + item_informacao, + item_informacao_descricao, + despesas_empenhadas, + despesas_liquidadas, + despesas_pagas, + + id_autor, + cargo_autor, + autor, + partido, + uf_autor, + url_foto_autor, + email_autor, + url_foto_partido, + + dt_ingest +FROM deduplicado \ No newline at end of file From b8760a305d63c0ff7fee1f9120e3fac50cbf75f4 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Mon, 27 Apr 2026 04:34:22 -0300 Subject: [PATCH 284/317] docs: schema atualizado com novas tabelas --- .../dados_abertos_dbt/bronze/schema.yml | 107 +++++++++++++ .../dados_abertos_dbt/silver/schema.yml | 64 ++++++++ .../mir/models/emendas_dbt/silver/schema.yml | 149 +++++++++++++++++- 3 files changed, 319 insertions(+), 1 deletion(-) diff --git a/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/schema.yml b/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/schema.yml index 13cbc8ba..cf6214d3 100644 --- a/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/schema.yml +++ b/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/bronze/schema.yml @@ -194,3 +194,110 @@ models: nome_tabela: 'senadores.senadores' nome_coluna: 'dt_ingest' tipo_esperado: 'timestamp with time zone' + + - name: deputados_historico + description: > + Tabela contendo todo o detalhamento evolutivo (histórico temporal) dos deputados federais através + da API da Câmara dos Deputados, incluindo trocas de mandatos, filiações, afastamentos e licenças, + bem como o começo e final calculado exato de cada período. + meta: + tags: + - bronze + columns: + - name: id + description: Identificador do deputado na Câmara. + - name: nome + description: Nome oficial do deputado na API da Câmara. + - name: sigla_partido + description: Partido politico atrelado ao registro do histórico de filiação. + - name: id_legislatura + description: Código numérico referente a legislatura do evento histórico do deputado. + - name: situacao + description: Situação legal ou institucional durante aquele período (ex. Exercício, Afastado, Licença, etc). + - name: data_filiacao + description: A data em que o estado do período temporal foi iniciado. Baseado na data evento reportada da Câmara. + - name: data_desfiliacao + description: > + A data fina de encerramento do vínculo/situação, derivado dinamicamente das + ocorrências subsequentes e do fim oficial da legislatura. + - name: dt_ingest + description: Data de ingestão do registro advinda do raw. + + tests: + - verificacao_tipagem: + nome_tabela: 'dados_abertos.deputados_historico' + nome_coluna: 'id' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'dados_abertos.deputados_historico' + nome_coluna: 'nome' + tipo_esperado: 'text' + - verificacao_tipagem: + nome_tabela: 'dados_abertos.deputados_historico' + nome_coluna: 'id_legislatura' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'dados_abertos.deputados_historico' + nome_coluna: 'situacao' + tipo_esperado: 'text' + - verificacao_tipagem: + nome_tabela: 'dados_abertos.deputados_historico' + nome_coluna: 'data_filiacao' + tipo_esperado: 'timestamp with time zone' + - verificacao_tipagem: + nome_tabela: 'dados_abertos.deputados_historico' + nome_coluna: 'data_desfiliacao' + tipo_esperado: 'timestamp with time zone' + + - name: senadores_historico + description: > + Tabela que detalha a temporalidade histórica de todas as filiações e trânsitos institucionais + recorrentes dos senadores federais pela API do Senado Federal. + meta: + tags: + - bronze + columns: + - name: parlamentar_id + description: Identificador base do senador. + - name: codigo_partido + description: Código associado com o registro partidário original do Senado. + - name: sigla_partido + description: Sigla partidária ao qual o legislador participava na época da filiação. + - name: nome_partido + description: Descritivo puro com o nome do partido atrelado a este registro historico. + - name: nome + description: Nome atrelado ao parlamentar nesta ocorrência temporal. + - name: data_filiacao + description: A data hora legal e literal em que a nova condição partidária foi assumida no senado. + - name: data_desfiliação + description: A data hora limítrofe oficial sinalizando que esse ciclo se acabou. + - name: ano_filiacao + description: Extração numérica do ano da vinculação que abriu o percurso. + - name: ano_desfiliação + description: Extração em inteiros do ano em que a ocorrência atingiu seu ponto derradeiro. + - name: fonte + description: Informação que aponta de qual engine o historico se baseia. + - name: dt_ingest + description: Timestamp padronizado indicando o horário de registro físico efetuado via job. + + tests: + - verificacao_tipagem: + nome_tabela: 'dados_abertos.senadores_historico' + nome_coluna: 'parlamentar_id' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'dados_abertos.senadores_historico' + nome_coluna: 'data_filiacao' + tipo_esperado: 'timestamp with time zone' + - verificacao_tipagem: + nome_tabela: 'dados_abertos.senadores_historico' + nome_coluna: 'data_desfiliação' + tipo_esperado: 'timestamp with time zone' + - verificacao_tipagem: + nome_tabela: 'dados_abertos.senadores_historico' + nome_coluna: 'ano_filiacao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'dados_abertos.senadores_historico' + nome_coluna: 'ano_desfiliação' + tipo_esperado: 'integer' diff --git a/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/silver/schema.yml b/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/silver/schema.yml index 48e0d81f..dd126f89 100644 --- a/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/silver/schema.yml +++ b/airflow_lappis/dags/dbt/mir/models/dados_abertos_dbt/silver/schema.yml @@ -78,3 +78,67 @@ models: nome_coluna: 'url_logo_partido' tipo_esperado: 'text' + - name: parlamentares_historico + description: > + Tabela da camada silver que consolida e uniformiza o panorama evolutivo de deputados e senadores, + fundindo as ocorrrências temporais (datas de filiação, desfiliação, cargo e status partidários). + meta: + tags: + - silver + columns: + - name: id_parlamentar + description: Identificador base unificado que representa o político (proveniente da Câmara ou Senado). + - name: chave_join_nome + description: Chave string de junção formatada em caixa alta e conversões 'unaccent' na rotina TRANSLATE projetada para merge rústico. + - name: nome_parlamentar + description: Nome extenso original proveniente da camada bronze agregada. + - name: cargo_parlamentar + description: Atribuição legal do período ('Deputado' ou 'Senador'). + - name: sigla_partido + description: Partido representativo desta dimensão temporal do parlamentar com padronização via mapas canônicos. + - name: uf_parlamentar + description: Sigla da Unidade Federativa. + - name: url_foto + description: Endereço linkado contendo a foto oficial da sub-entidade. + - name: email + description: Endereço de e-mail de correspondência institucional. + - name: data_filiacao + description: Timestamp contendo o marco de data original do início deste ciclo institucional vigente ou pregresso. + - name: data_desfiliacao + description: Timestamp determinando interrupção real do vínculo computada por cálculos de encerramento da sub-plataforma. + - name: id_legislatura + description: Inteiro referenciando o ciclo se existente (normalmente preenchido por Deputados). + - name: situacao + description: Estado textual representativo do cargo (Afastado, Exercício, Licença). + - name: fonte + description: Tracking text de procedência do pipeline primário. + - name: dt_ingest + description: Ponto de captura original no tempo. + - name: url_logo_partido + description: URL resolvida da logomarca do partido associado pelo macro map de partis. + + tests: + - verificacao_tipagem: + nome_tabela: 'dados_abertos.parlamentares_historico' + nome_coluna: 'id_parlamentar' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'dados_abertos.parlamentares_historico' + nome_coluna: 'chave_join_nome' + tipo_esperado: 'text' + - verificacao_tipagem: + nome_tabela: 'dados_abertos.parlamentares_historico' + nome_coluna: 'nome_parlamentar' + tipo_esperado: 'text' + - verificacao_tipagem: + nome_tabela: 'dados_abertos.parlamentares_historico' + nome_coluna: 'data_filiacao' + tipo_esperado: 'timestamp with time zone' + - verificacao_tipagem: + nome_tabela: 'dados_abertos.parlamentares_historico' + nome_coluna: 'data_desfiliacao' + tipo_esperado: 'timestamp with time zone' + - verificacao_tipagem: + nome_tabela: 'dados_abertos.parlamentares_historico' + nome_coluna: 'id_legislatura' + tipo_esperado: 'integer' diff --git a/airflow_lappis/dags/dbt/mir/models/emendas_dbt/silver/schema.yml b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/silver/schema.yml index 4500ba9f..cb8e2e9a 100644 --- a/airflow_lappis/dags/dbt/mir/models/emendas_dbt/silver/schema.yml +++ b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/silver/schema.yml @@ -165,4 +165,151 @@ models: - verificacao_tipagem: nome_tabela: 'emendas.planos_partidos' nome_coluna: 'dt_ingest' - tipo_esperado: 'timestamp with time zone' \ No newline at end of file + tipo_esperado: 'timestamp with time zone' + + - name: emendas_partidos + description: > + Tabela silver que enriquece os dados de emendas parlamentares do SIAFI-Tesouro com informações + cadastrais completas e históricas dos respectivos parlamentares e seus logotipos de partido. + Para a integração, avalia o nome com fallback temporal em relação ao momento de apresentação da emenda, + garantindo precisão na identificação de partido e cenário institucional à época. + meta: + tags: + - silver + columns: + - name: emissao_mes + description: Mês de emissão referencial da emenda orçamentária. + - name: emissao_dia + description: Data pontual exata de emissão referencial. + - name: codigo_programa + description: Código orçamentário numérico do programa de governo. + - name: programa + description: Descritivo do escopo do programa de governo que abrange o recurso. + - name: codigo_acao_ajustada + description: Código indexado da ação ajustada governamental. + - name: acao_ajustada + description: Descrição da ação de governo orçamentária que recebe o numerário. + - name: autor_emendas_orcamento_descricao + description: Informação bruta contendo a descrição completa do autor original contido no SIAFI. + - name: autor_emendas_orcamento_nome + description: Parte explícita do nome do autor destrinchado limpo no bronze, alvo primário de ligação. + - name: uf + description: Unidade federativa favorecida no destino dos recursos contidos na emenda. + - name: uf_descricao + description: Nome legível em extenso descritivo da UF. + - name: municipio + description: Localidade alvo municipal receptora contida. + - name: pais + description: Região país consolidada constante ('Brasil'). + - name: ne_ccor + description: Identificador de emissão originária gerando a conta corrente da Nota de Empenho (NE CCOR). + - name: ne_num_processo + description: Protocolo SEI base ou número rigoroso do processo público gerador original atrelado. + - name: ne_info_complementar + description: Repasse de dados textuais avulsos e complementares no interior descritivo da emenda. + - name: ne_ccor_descricao + description: Descritivo literal da Conta Corrente. + - name: doc_observacao + description: Comentários e observações de cadastro adicionadas ao corpo do documento governamental transacionado. + - name: codigo_gnd + description: Numérico estrito do Grupo de Natureza de Despesa. + - name: gnd + description: Rotulação formal designando a subfamília do Grupo de Natureza de Despesa onde os gastos repousam. + - name: natureza_despesa + description: Numérico formal indicativo da sub-natureza atrelada à despesa gerada. + - name: natureza_despesa_descricao + description: Descritivo em linguagem verbal detalhando a natureza final onde foi fixada e chancelada o repasse monetário. + - name: codigo_modalidade + description: Subcódigo identificador numérico atrelado à categoria sistêmica Modalidade de Aplicação. + - name: modalidade + description: Texto explícito definindo o perfil estrutural da finalidade e modalidade estritamente avaliadas desta verba. + - name: ne_ccor_favorecido + description: CPF ou credencial CNPJ de cunho privado/institucional validada do ente favorecido terminal da execução. + - name: ne_ccor_favorecido_descricao + description: Razão social declarada da entidade que irá usufruir e receber do orçamento. + - name: ne_ccor_ano_emissao + description: Marcador de referência em representação anual do dia inicial do empenho do tesouro reportado. + - name: ptres + description: Programa de Trabalho Resumido - indicador consolidado contábil da União no sistema do SIAFI. + - name: item_informacao + description: Hash ou item identificatório especial acoplado das métricas adicionais requeridas pelo processo contábil. + - name: item_informacao_descricao + description: Especificidade textual legível para auditoria relacionada fundamentalmente ao "item informação" do registro. + - name: despesas_empenhadas + description: Limite de fundos transacionados com base monetária ou saldo reservado à entidade recebedora na etapa de empenho. + - name: despesas_liquidadas + description: Montante e liquidez monetária contendo aprovação de entrega ou contraprestação referencial. + - name: despesas_pagas + description: Transferência monetária e emulação do recurso que se concretizou no processo de desembolso bancário explícito ao fornecedor. + - name: id_autor + description: Chave estrangeira extraída derivando-se da base histórica associatória dos parlamentares unificados. + - name: cargo_autor + description: Papel representacional (Senador, Deputado) assumido no ato em que emendou a peça pelo resgate de proximidade cronológica. + - name: autor + description: Nome purificado e resoluto encontrado na rede e camada silver para aquele político atuante. + - name: partido + description: Filiação ideológico-partidária oficial extraída da avaliação condicional temporal apurada ao tempo em que o documento subiu. + - name: uf_autor + description: Limite espacial contínuo estadual ou UF representativa que elegeu o ator governamental. + - name: url_foto_autor + description: Conexão estática direcionada vinculando o perfil institucional pictográfico extraído formalmente da web. + - name: email_autor + description: Espelho de conexão funcional do servidor federal que gerencia as ordens por mensagens do parlamento alvo. + - name: url_foto_partido + description: Logotipo e identidade diagramada iconográfica associada à sua chapa ou estandarte governamental daquele tempo exato. + - name: dt_ingest + description: Tracking text ou espelho de captura temporal da engrenagem do job (Airflow ETL) no formato padrão de UTC-3 timestamp timezone. + + tests: + - verificacao_tipagem: + nome_tabela: 'emendas.emendas_partidos' + nome_coluna: 'emissao_mes' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'emendas.emendas_partidos' + nome_coluna: 'emissao_dia' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'emendas.emendas_partidos' + nome_coluna: 'codigo_programa' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.emendas_partidos' + nome_coluna: 'codigo_gnd' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.emendas_partidos' + nome_coluna: 'codigo_modalidade' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.emendas_partidos' + nome_coluna: 'ne_ccor_ano_emissao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.emendas_partidos' + nome_coluna: 'ptres' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.emendas_partidos' + nome_coluna: 'despesas_empenhadas' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.emendas_partidos' + nome_coluna: 'despesas_liquidadas' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.emendas_partidos' + nome_coluna: 'despesas_pagas' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'emendas.emendas_partidos' + nome_coluna: 'id_autor' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'emendas.emendas_partidos' + nome_coluna: 'autor' + tipo_esperado: 'text' + - verificacao_tipagem: + nome_tabela: 'emendas.emendas_partidos' + nome_coluna: 'pais' + tipo_esperado: 'text' From b19eb0ee73369f743795ba6cda5f899fa97c4539 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Mon, 27 Apr 2026 04:43:46 -0300 Subject: [PATCH 285/317] feat: adiciona raws de historico no source --- airflow_lappis/dags/dbt/mir/models/sources.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/airflow_lappis/dags/dbt/mir/models/sources.yml b/airflow_lappis/dags/dbt/mir/models/sources.yml index 491a0b8d..18fc933c 100644 --- a/airflow_lappis/dags/dbt/mir/models/sources.yml +++ b/airflow_lappis/dags/dbt/mir/models/sources.yml @@ -21,11 +21,13 @@ sources: schema: camara_deputados tables: - name: deputados + - name: deputados_historico - name: senado_federal schema: senado_federal tables: - name: senadores + - name: senadores_historico - name: transfere_gov schema: transfere_gov From 45831695ec1b22a70894d25cdaac14b776efa323 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Mon, 27 Apr 2026 23:23:39 -0300 Subject: [PATCH 286/317] fix/clean-duplicates --- airflow_lappis/plugins/cliente_postgres.py | 57 +++++++++++++--------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/airflow_lappis/plugins/cliente_postgres.py b/airflow_lappis/plugins/cliente_postgres.py index adaf2385..f981a185 100755 --- a/airflow_lappis/plugins/cliente_postgres.py +++ b/airflow_lappis/plugins/cliente_postgres.py @@ -254,18 +254,18 @@ def get_nota_credito(self) -> List[Tuple[Any, ...]]: def remove_duplicates( self, table_name: str, column_mapping: Dict[int, str], schema: str = "siafi" ) -> None: - try: - columns = ", ".join(column_mapping.values()) - delete_query = f""" - DELETE FROM {schema}.{table_name} - WHERE ctid NOT IN ( - SELECT MIN(ctid) - FROM {schema}.{table_name} - GROUP BY {columns} - ); - """ - vacuum_query = f"VACUUM {schema}.{table_name};" + columns = ", ".join(column_mapping.values()) + delete_query = f""" + DELETE FROM {schema}.{table_name} + WHERE ctid NOT IN ( + SELECT MIN(ctid) + FROM {schema}.{table_name} + GROUP BY {columns} + ); + """ + vacuum_query = f"VACUUM {schema}.{table_name};" + try: logging.info( f"Executando query para remover duplicados em {schema}.{table_name}" ) @@ -277,21 +277,30 @@ def remove_duplicates( logging.info( f"Duplicados removidos com sucesso de {schema}.{table_name}" ) - - with psycopg2.connect(self.conn_str) as conn: - conn.autocommit = True - with conn.cursor() as cursor: - cursor.execute(vacuum_query) - logging.info( - f"VACUUM FULL executado com sucesso em {schema}.{table_name}" - ) - except Exception as e: logging.error( - f"Erro ao remover duplicados ou otimizar {schema}.{table_name}: {str(e)}" + f"Erro ao remover duplicados de {schema}.{table_name}: {str(e)}" ) raise + conn = None + try: + conn = psycopg2.connect(self.conn_str) + conn.autocommit = True + with conn.cursor() as cursor: + cursor.execute(vacuum_query) + logging.info(f"VACUUM executado com sucesso em {schema}.{table_name}") + except Exception as e: + logging.warning( + "Falha ao executar VACUUM em %s.%s (deduplicacao concluida): %s", + schema, + table_name, + str(e), + ) + finally: + if conn: + conn.close() + def get_codigo_unidade(self) -> list[dict]: query = """ SELECT codigounidade, ordem_grandeza @@ -354,7 +363,9 @@ def get_dashboard_raca_cor(self) -> List[Dict[str, Any]]: with psycopg2.connect(self.conn_str) as conn: with conn.cursor() as cursor: cursor.execute(query) - return [{"nome_cor": row[0], "valor": row[1]} for row in cursor.fetchall()] + return [ + {"nome_cor": row[0], "valor": row[1]} for row in cursor.fetchall() + ] def get_dashboard_situacao_funcional(self) -> List[Dict[str, Any]]: query = """ @@ -413,4 +424,4 @@ def get_dashboard_tabela_servidores(self, limit: int = 100) -> List[Dict[str, An "total": row[5], } for row in cursor.fetchall() - ] \ No newline at end of file + ] From 5f6cde43a92eeb6ea6332939d21cbb7df794bea8 Mon Sep 17 00:00:00 2001 From: Mateushqms Date: Tue, 28 Apr 2026 13:27:23 -0300 Subject: [PATCH 287/317] =?UTF-8?q?feat:=20camada=20silver=20e=20view=20pa?= =?UTF-8?q?ra=20TEDs=20=E2=80=94=20empenhos,=20NCs=20e=20Planos=20de=20A?= =?UTF-8?q?=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dags/dbt/mir/macros/udfs/f_format_nc.sql | 23 +- .../silver/empenhos_por_plano_acao.sql | 562 ++++++++++++++++++ .../empenhos_ted_dbt/silver/nc_plano_acao.sql | 25 + .../models/empenhos_ted_dbt/silver/schema.yml | 217 +++++++ .../views/num_transf_n_plano_acao.sql | 36 ++ .../models/empenhos_ted_dbt/views/schema.yml | 28 + 6 files changed, 887 insertions(+), 4 deletions(-) create mode 100644 airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/empenhos_por_plano_acao.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/nc_plano_acao.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/views/num_transf_n_plano_acao.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/views/schema.yml diff --git a/airflow_lappis/dags/dbt/mir/macros/udfs/f_format_nc.sql b/airflow_lappis/dags/dbt/mir/macros/udfs/f_format_nc.sql index f7a06c86..57b3f716 100644 --- a/airflow_lappis/dags/dbt/mir/macros/udfs/f_format_nc.sql +++ b/airflow_lappis/dags/dbt/mir/macros/udfs/f_format_nc.sql @@ -6,12 +6,27 @@ with pre_process as ( - select left(in_text, 7) as prefix, - right(in_text, 4)::numeric as posfix + select + left(in_text, 7) as prefix, + right(in_text, 4) as posfix_text + ), + + normalized as ( + select + prefix, + case + when posfix_text ~ '^[0-9]{1,4}$' then posfix_text::numeric + else null + end as posfix + from pre_process ) - select concat(prefix, to_char(posfix, 'FM00000')) as result - from pre_process + select + case + when posfix is null then null + else concat(prefix, to_char(posfix, 'FM00000')) + end as result + from normalized $$ language sql diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/empenhos_por_plano_acao.sql b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/empenhos_por_plano_acao.sql new file mode 100644 index 00000000..a982209d --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/empenhos_por_plano_acao.sql @@ -0,0 +1,562 @@ +with +empenhos_sem_vinculo_ted as( + select + *, + right(ne_ccor, 12) as ne, + left(ne_ccor,6) as orgao_id, + null as nc, + null as num_transf, + 'sem vinculo' as metodo + from {{ ref("empenhos_tesouro_ted") }} + where + ne_ccor_descricao ~* '\bTED[[:space:]:/().-]*(S/?[VN]|S/?VINCULO)' + or ne_ccor_descricao ~* 'SEM[[:space:]]+VINC[[:space:]]*(ULO|/TED)' +), +empenhos_filtrados as( + select + * + from {{ ref("empenhos_tesouro_ted") }} + where + ne_ccor_descricao !~* '\bTED[[:space:]:/().-]*(S/?[VN]|S/?VINCULO)' + and ne_ccor_descricao !~* 'SEM[[:space:]]+VINC[[:space:]]*(ULO|/TED)' + +), +empenhos_orgaos_metodo_1 as ( + select + *, + -- Uma série de extrações que servirão de identificadores + right(ne_ccor, 12) as ne, + left(ne_ccor,6) as orgao_id, + {{ target.schema }}.format_nc( + regexp_substr(ne_ccor_descricao, '([0-9]{4}NC[0-9]+)') + ) as nc, + replace( + (regexp_match( + ne_ccor_descricao, + '(FERENCIA|TED|CRICAO|TRANSF.|TRANF.|TRANF. |TRANSFERENCIA |TRANSFERENCIA:)(\s|^|-|)([0-9]{6}|1\w{5}|[0-9]{3}\.[0-9]{3})(\s|$|\.|,|-|\/)', + 'i' + ))[3], + '.', + '' + ) as num_transf, + 'metodo 1' as metodo + from empenhos_filtrados +), + +empenhos_restantes_metodo_1 as( +select * from empenhos_orgaos_metodo_1 where num_transf is null AND nc is null +), + +empenhos_orgaos_metodo_2 as ( + select + programa_governo, + programa_governo_descricao, + acao_governo, + acao_governo_descricao, + emissao_mes, + emissao_dia, + ne_ccor, + ne_num_processo, + ne_info_complementar, + ne_ccor_descricao, + doc_observacao, + natureza_despesa, + natureza_despesa_descricao, + ne_ccor_favorecido, + ne_ccor_favorecido_descricao, + ne_ccor_ano_emissao, + ptres, + fonte_recursos_detalhada, + fonte_recursos_detalhada_descricao, + despesas_empenhadas, + despesas_liquidadas, + despesas_pagas, + restos_a_pagar_inscritos, + restos_a_pagar_pagos, + dt_ingest, + ne, + orgao_id, + nc, + replace( + (regexp_match( + ne_ccor_descricao, + '.*(?:NOTA DE (TRANSFERENCIA|TRANFERENCIA|CREDITO))[:.[:space:]-]*((?=[A-Za-z0-9]*[0-9])[A-Za-z0-9]{6,})', + 'i' + ))[2], + '.', + '' + ) as num_transf, + 'metodo 2' as metodo + from empenhos_restantes_metodo_1 p +), + +empenhos_restantes_metodo_2 as( +select * from empenhos_orgaos_metodo_2 where num_transf is null AND nc is null +), + +empenhos_orgaos_metodo_3 as ( + select + programa_governo, + programa_governo_descricao, + acao_governo, + acao_governo_descricao, + emissao_mes, + emissao_dia, + ne_ccor, + ne_num_processo, + ne_info_complementar, + ne_ccor_descricao, + doc_observacao, + natureza_despesa, + natureza_despesa_descricao, + ne_ccor_favorecido, + ne_ccor_favorecido_descricao, + ne_ccor_ano_emissao, + ptres, + fonte_recursos_detalhada, + fonte_recursos_detalhada_descricao, + despesas_empenhadas, + despesas_liquidadas, + despesas_pagas, + restos_a_pagar_inscritos, + restos_a_pagar_pagos, + dt_ingest, + ne, + orgao_id, + nc, + replace( + (regexp_match( + ne_ccor_descricao, + '.*(?:(?:TED(?:[[:space:]]*[-.N∞øº°∅()]*))[[:space:]]*|(?:SIAFI[[:space:]]+N∫))[[:space:].-]*(?= 3 + AND ano_normalizado IS NOT NULL + GROUP BY orgao_id, numero_base +), + +empenhos_orgaos_metodo_7 as ( +SELECT + a.*, + g.ano_oficial, + CASE + WHEN length(a.numero_base) <= 2 + AND a.ano_normalizado is null + THEN NULL + + -- se o registro não tem ano, e o numero_base é "longo", e há um ano oficial no grupo -> preencher + WHEN a.ano_normalizado IS NULL + AND length(a.numero_base) >= 3 + AND g.ano_oficial IS NOT NULL + THEN a.numero_base || '/' || g.ano_oficial + + -- se o registro já tem ano_normalizado -> manter esse ano (normalizado) + WHEN a.ano_normalizado IS NOT NULL + THEN a.numero_base || '/' || a.ano_normalizado + + -- caso contrário (nenhum ano encontrado) -> deixar só o numero_base + ELSE a.numero_base + END AS numero_ted_normalizado +FROM norm_metodo_7 a +LEFT JOIN agrupado_metodo_7 g + ON a.orgao_id = g.orgao_id +AND a.numero_base = g.numero_base +), + +empenhos_restantes_metodo_7 as( + select * from empenhos_orgaos_metodo_7 + WHERE numero_ted_normalizado is null +), + +base_empenhos_orgaos_metodo_8 as ( +select + -- seleciona todas as colunas do órgãos 1, exceto nc e num_transf + emissao_mes,emissao_dia,ne_ccor,ne_num_processo,ne_info_complementar,ne_ccor_descricao,doc_observacao,natureza_despesa,natureza_despesa_descricao,ne_ccor_favorecido,ne_ccor_favorecido_descricao,ne_ccor_ano_emissao,ptres,fonte_recursos_detalhada,fonte_recursos_detalhada_descricao,despesas_empenhadas,despesas_liquidadas,despesas_pagas,restos_a_pagar_inscritos,restos_a_pagar_pagos,dt_ingest, ne,orgao_id,nc,num_transf,metodo, + trim(both ' -' from regexp_replace((regexp_match( + doc_observacao, + 'TED [[:space:].:NR∫º°-]*(?:([A-Za-zÀ-ÿ/][A-Za-zÀ-ÿ0-9/ \\-]*)[[:space:]\\-]+)?([0-9]{1,5}(?:[./ \\-][0-9]{2,4})?)', + 'i' + ))[1], '\s+', ' ', 'g')) AS complemento_ted, + replace((regexp_match( + doc_observacao, + 'TED [[:space:].:NR∫º°-]*(?:([A-Za-zÀ-ÿ/][A-Za-zÀ-ÿ0-9/ \\-]*)[[:space:]\\-]+)?([0-9]{1,5}(?:[./ \\-][0-9]{2,4})?)', + 'i' + ))[2], '.', '') AS num_ted, + 'metodo 2' as metodo_ted +from empenhos_restantes_metodo_7), + +base_metodo_8 AS ( + SELECT + *, + (regexp_match(num_ted, '^([0-9]{1,5})(?:[/.\- ]([0-9]{2,4}))?$'))[1] AS numero_base, + (regexp_match(num_ted, '^([0-9]{1,5})(?:[/.\- ]([0-9]{2,4}))?$'))[2] AS ano_raw + FROM base_empenhos_orgaos_metodo_8 +), +norm_metodo_8 AS ( + SELECT + *, + CASE + WHEN ano_raw IS NULL THEN NULL + WHEN length(ano_raw) = 2 THEN + CASE WHEN ano_raw::int <= 30 + THEN '20' || ano_raw -- 24 → 2024 + ELSE '19' || ano_raw -- 95 → 1995 + END + ELSE ano_raw + END AS ano_normalizado + FROM base_metodo_8 +), +agrupado_metodo_8 AS ( + -- calculamos o ano oficial APENAS para numero_base "longos" + SELECT + orgao_id, + numero_base, + MAX(ano_normalizado) AS ano_oficial + FROM norm_metodo_7 + WHERE length(numero_base) >= 3 + AND ano_normalizado IS NOT NULL + GROUP BY orgao_id, numero_base +), +empenhos_orgaos_metodo_8 as( + SELECT + a.*, + g.ano_oficial, + CASE + WHEN length(a.numero_base) <= 2 + AND a.ano_normalizado is null + THEN NULL + + -- se o registro não tem ano, e o numero_base é "longo", e há um ano oficial no grupo -> preencher + WHEN a.ano_normalizado IS NULL + AND length(a.numero_base) >= 3 + AND g.ano_oficial IS NOT NULL + THEN a.numero_base || '/' || g.ano_oficial + + -- se o registro já tem ano_normalizado -> manter esse ano (normalizado) + WHEN a.ano_normalizado IS NOT NULL + THEN a.numero_base || '/' || a.ano_normalizado + + -- caso contrário (nenhum ano encontrado) -> deixar só o numero_base + ELSE a.numero_base + END AS numero_ted_normalizado + FROM norm_metodo_8 a + LEFT JOIN agrupado_metodo_8 g + ON a.orgao_id = g.orgao_id + AND a.numero_base = g.numero_base + ), +empenhos_restantes_metodo_8 as( + select * from empenhos_orgaos_metodo_8 + WHERE numero_ted_normalizado is null +), + +union_metodo_7_8 as( +select * from empenhos_orgaos_metodo_7 WHERE numero_ted_normalizado is not null +UNION ALL +select * from empenhos_orgaos_metodo_8 WHERE numero_ted_normalizado is not null +UNION ALL +select * from empenhos_restantes_metodo_8), + +ids_agregados_num_ted_normalizado as( +select + orgao_id, + numero_ted_normalizado, + MAX(nc) AS nc, + MAX(num_transf) AS num_transf +from union_metodo_7_8 +GROUP BY orgao_id,numero_ted_normalizado +), + +empenhos_orgaos_metodo_9 AS ( +SELECT + ert.emissao_mes,ert.emissao_dia,ert.ne_ccor,ert.ne_num_processo,ert.ne_info_complementar,ert.ne_ccor_descricao,ert.doc_observacao,ert.natureza_despesa,ert.natureza_despesa_descricao,ert.ne_ccor_favorecido,ert.ne_ccor_favorecido_descricao,ert.ne_ccor_ano_emissao,ert.ptres,ert.fonte_recursos_detalhada,ert.fonte_recursos_detalhada_descricao,ert.despesas_empenhadas,ert.despesas_liquidadas,ert.despesas_pagas,ert.restos_a_pagar_inscritos,ert.restos_a_pagar_pagos,ert.dt_ingest, ert.ne,ert.orgao_id, + COALESCE(ert.nc, r.nc) AS nc, + COALESCE(ert.num_transf, r.num_transf) AS num_transf, + -- método calculado dinamicamente + CASE + WHEN (ert.nc IS NULL AND r.nc IS NOT NULL) + OR (ert.num_transf IS NULL AND r.num_transf IS NOT NULL) + THEN 'metodo 9' + ELSE ert.metodo + END AS metodo, + complemento_ted, num_ted, metodo_ted, numero_base, ano_raw, ano_normalizado, ano_oficial, + numero_ted_normalizado + FROM union_metodo_7_8 ert + LEFT JOIN ids_agregados_num_ted_normalizado r USING (orgao_id,numero_ted_normalizado) +), +empenhos_restantes_metodo_9 as ( + select * from empenhos_orgaos_metodo_9 where (nc != '') or (num_transf is not null) +), +planos_de_acao as ( + select * from {{ ref("num_transf_n_plano_acao") }} where plano_acao is not null +), +result_table as ( + select distinct er.*, pa.plano_acao::integer as plano_acao, pa.num_transf as num_transf_pa + from empenhos_restantes_metodo_9 er + left join planos_de_acao pa + on er.num_transf=CAST(pa.num_transf AS TEXT) +) -- + +select * +from result_table diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/nc_plano_acao.sql b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/nc_plano_acao.sql new file mode 100644 index 00000000..891d695e --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/nc_plano_acao.sql @@ -0,0 +1,25 @@ +{{ config(materialized="table") }} + +with + raw_data as ( + select * + from {{ ref("nc_unificado") }} + where nc_transferencia != '-8' + ), + + planos_de_acao as ( + select distinct * + from {{ ref("num_transf_n_plano_acao") }} + where plano_acao is not null + ), + + result_table as ( + select + rd.*, + pda.plano_acao::integer as id_plano_acao + from raw_data rd + left join planos_de_acao pda on rd.nc_transferencia = pda.num_transf + ) + +select * +from result_table diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/schema.yml b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/schema.yml index bc51f497..2cf5b406 100644 --- a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/schema.yml +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/silver/schema.yml @@ -223,3 +223,220 @@ models: nome_tabela: 'siafi_dbt.nc_unificado' nome_coluna: 'dt_ingest' tipo_esperado: 'timestamp with time zone' + + - name: nc_plano_acao + description: > + Tabela silver que parte de nc_unificado e enriquece com o identificador do Plano de Ação + do TransfereGov (id_plano_acao) via a ponte num_transf_n_plano_acao (nc_transferencia -> plano_acao). + Filtra registros com nc_transferencia = '-8' (NCs sem vínculo de transferência válido). + Retorna todas as colunas de nc_unificado acrescidas de id_plano_acao, que pode ser nulo + quando a NC do SIAFI não possui correspondência no TransfereGov. + meta: + tags: + - silver + columns: + - name: id_plano_acao + description: > + Identificador do Plano de Ação no TransfereGov (integer), obtido via ponte + num_transf_n_plano_acao pelo nc_transferencia. Nulo quando a NC não possui + correspondência no TransfereGov. + - name: programa_governo + description: "Código do programa de governo (SIAFI, somente dados pré-2026; enriquecido por PTRES via nc_unificado)." + - name: programa_governo_descricao + description: "Descrição do programa de governo (SIAFI, somente dados pré-2026; enriquecido por PTRES via nc_unificado)." + - name: acao_governo + description: "Código da ação de governo (SIAFI, somente dados pré-2026; enriquecido por PTRES via nc_unificado)." + - name: acao_governo_descricao + description: "Descrição da ação de governo (SIAFI, somente dados pré-2026; enriquecido por PTRES via nc_unificado)." + - name: nc + description: "Número completo da Nota de Crédito no SIAFI. Primeiros 6 dígitos = UG emitente; últimos 12 = identificador da NC." + - name: nc_transferencia + description: "Número da transferência TED associado à NC — chave de ligação com a ponte num_transf_n_plano_acao." + - name: nc_fonte_recursos + description: "Código da fonte de recursos da NC (pré-2026: nc_fonte_recursos; pós-2026: fonte_codigo)." + - name: nc_fonte_recursos_descricao + description: "Descrição da fonte de recursos (pré-2026: nc_fonte_recursos_descricao; pós-2026: fonte_nome)." + - name: ptres + description: "Programa de Trabalho Resumido (PTRES) vinculado à NC no SIAFI." + - name: nc_evento_descricao + description: "Descrição do evento contábil (pré-2026: nc_evento_descricao; pós-2026: tipo_nc)." + - name: nc_ug_responsavel + description: "Código da UG responsável pela NC (pré-2026: nc_ug_responsavel; pós-2026: emitente_codigo)." + - name: nc_ug_responsavel_descricao + description: "Nome da UG responsável (pré-2026: nc_ug_responsavel_descricao; pós-2026: emitente_nome)." + - name: nc_natureza_despesa + description: "Código da natureza de despesa (pré-2026: nc_natureza_despesa; pós-2026: gnd_codigo)." + - name: nc_natureza_despesa_descricao + description: "Descrição da natureza de despesa (pré-2026: nc_natureza_despesa_descricao; pós-2026: gnd_nome)." + - name: nc_plano_interno + description: "Código do plano interno da NC (pré-2026: nc_plano_interno; pós-2026: pi_codigo)." + - name: nc_plano_interno_descricao1 + description: "Descrição principal do plano interno (pré-2026: nc_plano_interno_descricao1; pós-2026: pi_nome)." + - name: nc_plano_interno_descricao2 + description: "Descrição secundária do plano interno. Somente dados pré-2026; nulo para pós-2026." + - name: favorecido_doc + description: "CPF/CNPJ do favorecido (pré-2026: favorecido_doc; pós-2026: favorecido_codigo)." + - name: favorecido_doc_descricao + description: "Nome ou razão social do favorecido (pré-2026: favorecido_doc_descricao; pós-2026: favorecido_nome)." + - name: favorecido_municipio + description: "Código do município do favorecido. Somente dados pré-2026; nulo para pós-2026." + - name: favorecido_municipio_descricao + description: "Nome do município do favorecido. Somente dados pré-2026; nulo para pós-2026." + - name: valor_celula + description: "Valor financeiro da linha da NC (numeric). Pré-2026: nc_valor_linha; pós-2026: valor_celula. Sem correção de sinal." + - name: movimento_liquido_moeda_origem + description: "Movimento líquido na moeda de origem. Pré-2026: campo homônimo; pós-2026: total_lista." + - name: descricao + description: "Descrição geral da NC. Somente dados pós-2026; nulo para pré-2026." + - name: nc_evento + description: "Código do evento contábil da NC. Somente dados pré-2026; nulo para pós-2026. Usado no gold para classificar orcamento_recebido/devolvido." + - name: nc_item_detalhamento + description: "Indicador S/N de detalhamento do item da NC. Somente dados pós-2026; nulo para pré-2026." + - name: emissao_dia + description: "Data de emissão da NC (date). Somente dados pós-2026; nulo para pré-2026." + - name: emissao_mes + description: "Mês de emissão da NC (text). Somente dados pós-2026; nulo para pré-2026." + - name: emissao_ano + description: "Ano de emissão da NC (text). Somente dados pós-2026; nulo para pré-2026." + - name: ro + description: "Registro de Operação associado à NC. Somente dados pós-2026; nulo para pré-2026." + - name: dc + description: "Indicador Débito/Crédito da NC. Somente dados pós-2026; nulo para pré-2026." + - name: total_lista + description: "Valor total da lista de itens da NC (numeric). Somente dados pós-2026; nulo para pré-2026." + - name: esfera_orcamentaria_codigo + description: "Código da esfera orçamentária. Somente dados pós-2026; nulo para pré-2026." + - name: esfera_orcamentaria_nome + description: "Nome da esfera orçamentária. Somente dados pós-2026; nulo para pré-2026." + - name: dt_ingest + description: "Timestamp de ingestão do SIAFI (timestamptz, UTC-3)." + tests: + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.nc_plano_acao' + nome_coluna: 'id_plano_acao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.nc_plano_acao' + nome_coluna: 'valor_celula' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.nc_plano_acao' + nome_coluna: 'emissao_dia' + tipo_esperado: 'date' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.nc_plano_acao' + nome_coluna: 'dt_ingest' + tipo_esperado: 'timestamp with time zone' + + - name: empenhos_por_plano_acao + description: > + Tabela silver que vincula as Notas de Empenho do SIAFI aos Planos de Ação do TransfereGov. + O vínculo é desafiador porque a NE não possui um campo estruturado para o número de + transferência — ele está embutido no campo livre ne_ccor_descricao ou em campos auxiliares. + O modelo aplica 9 métodos sequenciais de extração via regex, processando cada NE apenas + uma vez pelo primeiro método que obtiver resultado. Ao final, cruza o num_transf encontrado + com a view num_transf_n_plano_acao para obter o plano_acao correspondente. + meta: + tags: + - silver + columns: + - name: emissao_mes + description: "Mês de emissão da NE no SIAFI" + - name: emissao_dia + description: "Data de emissão da NE no SIAFI" + - name: ne_ccor + description: "Chave completa da Nota de Empenho no SIAFI" + - name: ne_num_processo + description: "Número do processo administrativo associado à NE, limpo de pontos, barras e hifens." + - name: ne_info_complementar + description: "Campo complementar da NE. Usado pelo método 5 de extração: quando contém exatamente 6 dígitos ou padrão 1XXXXX, é tratado como num_transf." + - name: ne_ccor_descricao + description: "Descrição livre da NE — campo principal de onde os métodos 1 a 5 e 7 extraem o num_transf via regex." + - name: doc_observacao + description: "Campo de observação do documento associado à NE. Usado pelo método 8 de extração via padrão 'TED NNN/AAAA'." + - name: natureza_despesa + description: "Código da natureza de despesa da NE (ex: 339030)." + - name: natureza_despesa_descricao + description: "Descrição da natureza de despesa da NE." + - name: ne_ccor_favorecido + description: "CNPJ ou CPF do favorecido do empenho, padronizado em maiúsculas." + - name: ne_ccor_favorecido_descricao + description: "Nome ou razão social do favorecido da NE." + - name: ne_ccor_ano_emissao + description: "Ano de emissão da NE (integer, YYYY), validado via regex para garantir 4 dígitos numéricos." + - name: ptres + description: "Programa de Trabalho Resumido (PTRES) vinculado à NE no SIAFI." + - name: fonte_recursos_detalhada + description: "Código detalhado da fonte de recursos da NE." + - name: fonte_recursos_detalhada_descricao + description: "Descrição da fonte de recursos. Usado pelo método 4 de extração do num_transf via regex no padrão 'TED: NNNNNN'." + - name: despesas_empenhadas + description: "Valor empenhado da NE (numeric 15,2). Positivo = novo empenho; negativo = anulação de empenho." + - name: despesas_liquidadas + description: "Valor liquidado (numeric 15,2) — despesas com bens ou serviços confirmados como entregues." + - name: despesas_pagas + description: "Valor efetivamente pago no exercício corrente (numeric 15,2)." + - name: restos_a_pagar_inscritos + description: "Valor inscrito em Restos a Pagar (numeric 15,2) — empenhado e não pago até o encerramento do exercício." + - name: restos_a_pagar_pagos + description: "Valor de Restos a Pagar efetivamente quitado (numeric 15,2)." + - name: dt_ingest + description: "Timestamp de ingestão da NE no SIAFI (timestamptz, UTC-3)." + - name: ne + description: "Identificador curto da Nota de Empenho — últimos 12 caracteres de ne_ccor." + - name: orgao_id + description: "Código do órgão emitente da NE — primeiros 6 caracteres de ne_ccor." + - name: nc + description: "Número da Nota de Crédito associada à NE, extraído de ne_ccor_descricao via regex e formatado pela UDF format_nc." + - name: num_transf + description: "Número da transferência TED extraído da NE pelos métodos 1 a 9. Chave de ligação com a ponte num_transf_n_plano_acao." + - name: metodo + description: > + Identifica qual método resolveu o vínculo da NE. Valores possíveis: + 'sem vinculo' (NEs com TED explícito sem número), 'metodo 1' a 'metodo 9', + 'ted ou nc invalido' e 'vinculo nao encontrado' (sem vínculo identificado). + - name: complemento_ted + description: "Complemento textual do TED extraído antes do número (ex: nome do programa), produzido pelos métodos 7 e 8." + - name: num_ted + description: "Número bruto do TED extraído do campo de texto pelos métodos 7 e 8, antes da normalização de ano." + - name: metodo_ted + description: "Indica a origem do numero_ted_normalizado: 'metodo 1' (ne_ccor_descricao) ou 'metodo 2' (doc_observacao)." + - name: numero_base + description: "Parte numérica do TED sem o ano, extraída de num_ted para o processo de normalização." + - name: ano_raw + description: "Ano bruto extraído de num_ted antes da normalização (pode ser 2 ou 4 dígitos)." + - name: ano_normalizado + description: "Ano com 4 dígitos após normalização: anos de 2 dígitos <= 30 viram '20XX'; > 30 viram '19XX'." + - name: ano_oficial + description: "Ano mais recente observado para o mesmo (orgao_id, numero_base), usado para preencher NEs sem ano explícito." + - name: numero_ted_normalizado + description: "Número do TED no formato canônico 'numero_base/ano_oficial' (ex: '42/2024'). Nulo quando nenhum ano pôde ser determinado com numero_base curto (<= 2 dígitos)." + - name: plano_acao + description: "Identificador do Plano de Ação no TransfereGov (integer), obtido via ponte num_transf_n_plano_acao a partir do num_transf. Nulo para NEs sem correspondência." + - name: num_transf_pa + description: "num_transf conforme registrado na ponte num_transf_n_plano_acao — usado para conferência cruzada com o num_transf extraído da NE." + tests: + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.empenhos_por_plano_acao' + nome_coluna: 'despesas_empenhadas' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.empenhos_por_plano_acao' + nome_coluna: 'despesas_liquidadas' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.empenhos_por_plano_acao' + nome_coluna: 'restos_a_pagar_inscritos' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.empenhos_por_plano_acao' + nome_coluna: 'ne_ccor_ano_emissao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.empenhos_por_plano_acao' + nome_coluna: 'plano_acao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.empenhos_por_plano_acao' + nome_coluna: 'dt_ingest' + tipo_esperado: 'timestamp with time zone' diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/views/num_transf_n_plano_acao.sql b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/views/num_transf_n_plano_acao.sql new file mode 100644 index 00000000..4bede748 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/views/num_transf_n_plano_acao.sql @@ -0,0 +1,36 @@ +{{ config(materialized="view") }} + +with + + nc_transfere_gov as ( + select distinct id_plano_acao, tx_numero_nota as nc, ndc.cd_ug_emitente_nota as ug + from {{ source("transfere_gov", "notas_de_credito") }} ndc + where ndc.tx_numero_nota is not null + ), + + nc_siafi as ( + select distinct + left(nc, 6) as ug, right(nc, 12) as nc, nt.nc_transferencia as num_transf + from {{ref("nc_tesouro_mir")}} nt + where nc_transferencia != '-8' + ), + + joined as ( + select distinct num_transf, id_plano_acao as plano_acao + from nc_siafi + left join nc_transfere_gov using (nc, ug) + ), + + ranked as ( + select + *, + row_number() over ( + partition by num_transf + order by case when plano_acao is not null then 1 else 2 end + ) as rn + from joined + ) + +select num_transf, plano_acao +from ranked +where rn = 1 diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/views/schema.yml b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/views/schema.yml new file mode 100644 index 00000000..25f2d8bb --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/views/schema.yml @@ -0,0 +1,28 @@ +version: 2 + +models: + + - name: num_transf_n_plano_acao + description: > + View de mapeamento entre o número de transferência do SIAFI (num_transf / nc_transferencia) + e o identificador do Plano de Ação do TransfereGov (plano_acao / id_plano_acao). + É a ponte central que conecta os dois sistemas. + + O cruzamento é feito via Notas de Crédito: o SIAFI registra a NC com nc_transferencia + e o TransfereGov registra a mesma NC com id_plano_acao. A correspondência é feita + pela chave composta (nc, ug_emitente). + meta: + tags: + - view + columns: + - name: num_transf + description: > + Número da transferência TED conforme registrado no SIAFI (nc_transferencia). + Chave de entrada — é o identificador que NCs, NEs e PFs usam para referenciar + uma transferência específica. + - name: plano_acao + description: > + Identificador do Plano de Ação no TransfereGov (id_plano_acao), obtido via + cruzamento da NC entre SIAFI e TransfereGov. Pode ser nulo quando a NC do SIAFI + não possui correspondência no TransfereGov. Tipo text na view — consumidores + devem fazer cast para integer conforme necessário. From 947dab54132f7b79136f9d94a17327893585c90e Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira <143732704+davi-aguiar-vieira@users.noreply.github.com> Date: Tue, 28 Apr 2026 15:40:42 -0300 Subject: [PATCH 288/317] fix/ISO-format (#235) --- airflow_lappis/plugins/cliente_sqlserver.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/airflow_lappis/plugins/cliente_sqlserver.py b/airflow_lappis/plugins/cliente_sqlserver.py index 59d7d220..08778bad 100644 --- a/airflow_lappis/plugins/cliente_sqlserver.py +++ b/airflow_lappis/plugins/cliente_sqlserver.py @@ -1,6 +1,7 @@ import logging import re import math +from datetime import date, datetime, time from typing import Any, Dict, List, Tuple from airflow.providers.microsoft.mssql.hooks.mssql import MsSqlHook @@ -37,6 +38,9 @@ def _sanitize_value(value: Any) -> Any: if str(value) == "NaT": return None + if isinstance(value, (datetime, date, time)): + return value.isoformat() + return value def fetch_table_all( From f0f7ecff792fecbe26e43989a68ab0664ee46984 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Wed, 29 Apr 2026 09:20:59 -0300 Subject: [PATCH 289/317] feat: metodo senadores atuais --- airflow_lappis/plugins/cliente_senadores.py | 37 +++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/airflow_lappis/plugins/cliente_senadores.py b/airflow_lappis/plugins/cliente_senadores.py index 3517cd61..def44a53 100644 --- a/airflow_lappis/plugins/cliente_senadores.py +++ b/airflow_lappis/plugins/cliente_senadores.py @@ -121,3 +121,40 @@ def get_filiacoes_senador(self, senador_id: int | str) -> list[dict[str, Any]] | f"senador_id={senador_id} with status: {status}" ) return None + + def get_senadores_atuais(self) -> list: + """ + Obtém a lista de senadores em exercício. + O Senado geralmente retorna tudo em uma única chamada, + sem paginação complexa como a Câmara. + """ + endpoint = "/senador/lista/atual" + logging.info("[cliente_senadores.py] Fetching senadores atuais") + + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER + ) + + if status == http.HTTPStatus.OK and isinstance(data, dict): + # Estrutura esperada: ListaParlamentarEmExercicio -> + # Parlamentares -> Parlamentar. + try: + lista_root = data.get("ListaParlamentarEmExercicio", {}) + parlamentares = lista_root.get("Parlamentares", {}).get("Parlamentar", []) + + if isinstance(parlamentares, dict): + parlamentares = [parlamentares] + + logging.info( + "[cliente_senadores.py] Successfully fetched " + f"{len(parlamentares)} senadores" + ) + return parlamentares + except Exception as e: + logging.error( + f"[cliente_senadores.py] Erro ao parsear JSON do Senado: {e}" + ) + return [] + else: + logging.warning(f"[cliente_senadores.py] Failed with status: {status}") + return [] From 2e2643d5c04cb579810c4fc802c34b3a6adf9778 Mon Sep 17 00:00:00 2001 From: alvesingrid Date: Wed, 29 Apr 2026 15:26:33 -0300 Subject: [PATCH 290/317] feat: adicionando logo partidos --- .../dados_abertos/logo_partidos_dag.py | 79 +++++++++++++++++ .../dags/dbt/mir/models/sources.yml | 4 + airflow_lappis/plugins/cliente_partidos.py | 86 +++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 airflow_lappis/dags/data_ingest/dados_abertos/logo_partidos_dag.py create mode 100644 airflow_lappis/plugins/cliente_partidos.py diff --git a/airflow_lappis/dags/data_ingest/dados_abertos/logo_partidos_dag.py b/airflow_lappis/dags/data_ingest/dados_abertos/logo_partidos_dag.py new file mode 100644 index 00000000..42694b9d --- /dev/null +++ b/airflow_lappis/dags/data_ingest/dados_abertos/logo_partidos_dag.py @@ -0,0 +1,79 @@ +import logging +import time +from airflow.decorators import dag, task +from datetime import datetime, timedelta +from schedule_loader import get_dynamic_schedule +from postgres_helpers import get_postgres_conn +from cliente_partidos import ClientePartidos +from cliente_postgres import ClientPostgresDB + +@dag( + schedule_interval=get_dynamic_schedule("logo_partidos_dag"), + start_date=datetime(2025, 1, 1), + catchup=False, + default_args={ + "owner": "Ingrid", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["logo_partidos", "partidos", "dados_abertos", "MIR"], +) +def logo_partidos_dag() -> None: + """DAG para buscar e armazenar dados de partidos e seus logos da Câmara dos Deputados.""" + + @task + def fetch_and_store_partidos() -> None: + logging.info("[logo_partidos_dag.py] Iniciando extração de partidos") + + api = ClientePartidos() + postgres_conn_str = get_postgres_conn("postgres_mir") + db = ClientPostgresDB(postgres_conn_str) + + partidos_basicos = api.get_all_partidos() + + partidos_completos = [] + + if partidos_basicos: + for p in partidos_basicos: + partido_id = p.get("id") + if partido_id: + detalhe = api.get_partido_by_id(partido_id) + if detalhe: + registro = { + "id": detalhe.get("id"), + "sigla": detalhe.get("sigla"), + "nome": detalhe.get("nome"), + "uri": detalhe.get("uri"), + "urllogo": detalhe.get("urlLogo"), + "dt_ingest": datetime.now().isoformat() + } + partidos_completos.append(registro) + + time.sleep(0.5) + + logging.info( + f"[logo_partidos_dag.py] Inserindo " + f"{len(partidos_completos)} partidos no schema camara_deputados" + ) + + if partidos_completos: + db.insert_data( + partidos_completos, + "logo_partidos", + conflict_fields=["id"], + primary_key=["id"], + schema="dados_abertos", + ) + + logging.info( + f"[logo_partidos_dag.py] Concluído. " + f"Total de {len(partidos_completos)} registros processados." + ) + else: + logging.warning("[logo_partidos_dag.py] Nenhum dado de detalhe retornado para os partidos.") + else: + logging.warning("[logo_partidos_dag.py] Nenhum partido encontrado na lista principal.") + + fetch_and_store_partidos() + +logo_partidos_dag() \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/sources.yml b/airflow_lappis/dags/dbt/mir/models/sources.yml index 5827fd7a..f286f868 100644 --- a/airflow_lappis/dags/dbt/mir/models/sources.yml +++ b/airflow_lappis/dags/dbt/mir/models/sources.yml @@ -27,6 +27,10 @@ sources: tables: - name: senadores - name: legislaturas + - name: dados_abertos + schema: dados_abertos + tables: + - name: logo_partidos - name: transfere_gov schema: transfere_gov diff --git a/airflow_lappis/plugins/cliente_partidos.py b/airflow_lappis/plugins/cliente_partidos.py new file mode 100644 index 00000000..c6d7b363 --- /dev/null +++ b/airflow_lappis/plugins/cliente_partidos.py @@ -0,0 +1,86 @@ +import http +import logging +from typing import Any +from cliente_base import ClienteBase + + +class ClientePartidos(ClienteBase): + """ + Cliente para consumir a API de Dados Abertos da Câmara dos Deputados para pegar a logo dos partidos. + """ + + BASE_URL = "https://dadosabertos.camara.leg.br/api/v2" + BASE_HEADER = {"accept": "application/json"} + + def __init__(self) -> None: + super().__init__(base_url=ClientePartidos.BASE_URL) + logging.info( + "[cliente_partidos.py] Initialized ClientePartidos with base_url: " + f"{ClientePartidos.BASE_URL}" + ) + + def get_partidos(self, **params: Any) -> list: + """ + Obter lista de partidos + """ + endpoint = "/partidos" + logging.info(f"[cliente_partidos.py] Fetching partidos with params: {params}") + + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER, params=params + ) + + if status == http.HTTPStatus.OK and isinstance(data, dict): + partidos: list[dict[str, Any]] = data.get("dados", []) + logging.info( + f"[cliente_partidos.py] Successfully fetched {len(partidos)} partidos" + ) + return partidos + else: + logging.warning( + f"[cliente_partidos.py] Failed to fetch partidos with status: {status}" + ) + return None + + def get_all_partidos(self) -> list: + """ + Itera por todas as páginas da API e retorna a lista completa de partidos. + """ + all_partidos = [] + pagina = 1 + + while True: + params = {"pagina": pagina, "itens": 100, "ordem": "ASC", "ordenarPor": "sigla"} + partidos = self.get_partidos(**params) + + if not partidos: + break + + all_partidos.extend(partidos) + + if len(partidos) < 100: + break + + pagina += 1 + + return all_partidos + + def get_partido_by_id(self, partido_id: int) -> dict: + """ + Obter detalhes de um partido específico pelo ID + """ + endpoint = f"/partidos/{partido_id}" + logging.info(f"[cliente_partidos.py] Fetching partido ID: {partido_id}") + + status, data = self.request( + http.HTTPMethod.GET, endpoint, headers=self.BASE_HEADER + ) + + if status == http.HTTPStatus.OK and isinstance(data, dict): + partido: dict[str, Any] = data.get("dados", {}) + return partido + else: + logging.warning( + f"[cliente_partidos.py] Failed to fetch partido {partido_id} with status: {status}" + ) + return None From 347499e02ad6c6ca157a7fff55d1daa0f727f598 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Wed, 29 Apr 2026 22:28:37 -0300 Subject: [PATCH 291/317] =?UTF-8?q?fix:=20preven=C3=A7=C3=A3o=20de=20nomes?= =?UTF-8?q?=20colunas=20cliente=20postgres?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- airflow_lappis/plugins/cliente_postgres.py | 55 ++++++++++++++++----- airflow_lappis/plugins/cliente_senadores.py | 14 +++++- 2 files changed, 54 insertions(+), 15 deletions(-) diff --git a/airflow_lappis/plugins/cliente_postgres.py b/airflow_lappis/plugins/cliente_postgres.py index adaf2385..627c5e77 100755 --- a/airflow_lappis/plugins/cliente_postgres.py +++ b/airflow_lappis/plugins/cliente_postgres.py @@ -101,13 +101,16 @@ def insert_data( ) return + flattened_data = self._flatten_data(data) + columns = list(flattened_data[0].keys()) + column_probe = {col: None for col in columns} + self.create_table_if_not_exists( - data[0], table_name, primary_key=primary_key, schema=schema, conn=conn + column_probe, table_name, primary_key=primary_key, schema=schema, conn=conn ) + self.alter_table(column_probe, table_name, schema=schema, conn=conn) - flattened_data = self._flatten_data(data) - columns = list(flattened_data[0].keys()) - values = [tuple(item.values()) for item in flattened_data] + values = [tuple(item.get(col) for col in columns) for item in flattened_data] sql = f"INSERT INTO {schema}.{table_name} ({', '.join(columns)}) VALUES %s" @@ -123,6 +126,20 @@ def _execute(connection): logging.info( f"[cliente_postgres.py] Inserted data into {schema}.{table_name}" ) + except psycopg2.errors.UndefinedColumn as err: + logging.warning( + f"[cliente_postgres.py] Missing column detected in {schema}.{table_name}: " + f"{err}. Tentando alterar tabela e reinserir." + ) + connection.rollback() + column_probe = {col: None for col in columns} + self.alter_table( + column_probe, table_name, schema=schema, conn=connection + ) + psycopg2.extras.execute_values(cursor, sql, values) + logging.info( + f"[cliente_postgres.py] Inserted data into {schema}.{table_name} after alter" + ) except psycopg2.Error as err: logging.error( f"[cliente_postgres.py] Failed to insert data into {schema}." @@ -201,13 +218,17 @@ def get_programacao_financeira(self) -> List[Tuple[Any, ...]]: return cursor.fetchall() def alter_table( - self, data: Dict[str, Any], table_name: str, schema: str = "raw" + self, + data: Dict[str, Any], + table_name: str, + schema: str = "raw", + conn=None, ) -> None: flattened_data = self._flatten_data([data])[0] columns = list(flattened_data.keys()) - with psycopg2.connect(self.conn_str) as conn: - with conn.cursor() as cursor: + def _execute(connection): + with connection.cursor() as cursor: cursor.execute( f""" SELECT column_name @@ -236,10 +257,16 @@ def alter_table( f"to {schema}.{table_name}. Error: {str(e)}" ) - conn.commit() - logging.info( - f"[cliente_postgres.py] Table {schema}.{table_name} altered successfully" - ) + if conn is not None: + _execute(conn) + else: + with psycopg2.connect(self.conn_str) as new_conn: + _execute(new_conn) + new_conn.commit() + + logging.info( + f"[cliente_postgres.py] Table {schema}.{table_name} altered successfully" + ) def get_nota_credito(self) -> List[Tuple[Any, ...]]: query = ( @@ -354,7 +381,9 @@ def get_dashboard_raca_cor(self) -> List[Dict[str, Any]]: with psycopg2.connect(self.conn_str) as conn: with conn.cursor() as cursor: cursor.execute(query) - return [{"nome_cor": row[0], "valor": row[1]} for row in cursor.fetchall()] + return [ + {"nome_cor": row[0], "valor": row[1]} for row in cursor.fetchall() + ] def get_dashboard_situacao_funcional(self) -> List[Dict[str, Any]]: query = """ @@ -413,4 +442,4 @@ def get_dashboard_tabela_servidores(self, limit: int = 100) -> List[Dict[str, An "total": row[5], } for row in cursor.fetchall() - ] \ No newline at end of file + ] diff --git a/airflow_lappis/plugins/cliente_senadores.py b/airflow_lappis/plugins/cliente_senadores.py index def44a53..31d473a8 100644 --- a/airflow_lappis/plugins/cliente_senadores.py +++ b/airflow_lappis/plugins/cliente_senadores.py @@ -103,13 +103,23 @@ def get_filiacoes_senador(self, senador_id: int | str) -> list[dict[str, Any]] | if status == http.HTTPStatus.OK and isinstance(data, dict): try: - root = data.get("ListaFiliacoesParlamentar", {}) + root = data.get("FiliacaoParlamentar", data.get("ListaFiliacoesParlamentar", {})) + parlamentar = root.get("Parlamentar") + if isinstance(parlamentar, dict): + root = parlamentar + filiacao = root.get("Filiacoes", {}).get("Filiacao", []) if isinstance(filiacao, dict): return [filiacao] if isinstance(filiacao, list): - return filiacao + if filiacao: + return filiacao + logging.warning( + "[cliente_senadores.py] Filiacoes vazias para " + f"senador_id={senador_id}" + ) + return None except Exception as e: logging.error( "[cliente_senadores.py] Erro ao parsear filiacoes do senador " From aa7b3d997936350fbd3c81f45fe9e8f2f1050bd8 Mon Sep 17 00:00:00 2001 From: Lucas Bottino Date: Thu, 30 Apr 2026 10:08:55 -0300 Subject: [PATCH 292/317] =?UTF-8?q?docs:=20atualiza=20documenta=C3=A7?= =?UTF-8?q?=C3=A3o=20do=20readme=20e=20traduz=20para=20portugu=C3=AAs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 113 +++++++++++++++++++++++++++++------------------------- 1 file changed, 61 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 97c3b701..deb627b7 100644 --- a/README.md +++ b/README.md @@ -34,76 +34,84 @@ Para dúvidas, sugestões ou para contribuir com o projeto, entre em contato con # Data Pipeline Project -This project implements a modern data stack using Airflow, dbt, Jupyter, and Superset for data orchestration, transformation, analysis, and visualization. +O Data Pipeline Project é uma solução moderna que utiliza ferramentas como Airflow, DBT, Jupyter e Superset para orquestração, transformação, análise e visualização de dados. -## 🚀 Stack Components +## 🚀 Stack do projeto -- **Apache Airflow**: Workflow orchestration -- **dbt**: Data transformation -- **Jupyter**: Interactive data analysis -- **Apache Superset**: Data visualization and exploration -- **Docker**: Containerization and local development -- **Make**: Build automation and setup +- **Apache Airflow**: Orquestração de workflows +- **dbt**: Transformação de dados +- **Jupyter**: Análise de dados interativa +- **Apache Superset**: Visualização e exploração de dados +- **Docker**: Containerização e desenvolvimento local +- **Make**: Automação de build e configuração -## 📋 Prerequisites +## 📋 Pré-requisitos -- Docker and Docker Compose +- Docker e Docker Compose - Make -- Python 3.x +- Python 3.11.x - Git ## 🔧 Setup -1. Clone the repository: +1. Clone o repositório: ```bash -git clone git@gitlab.com:lappis-unb/gest-odadosipea/app-lappis-ipea.git -cd app-lappis-ipea +git clone git@github.com:GovHub-br/data-application-gov-hub.git +cd data-application-gov-hub ``` -2. Run the setup using Make: +2. Execute a configuração usando Make: ```bash make setup ``` -This will: -- Create necessary virtual environments -- Install dependencies -- Set up pre-commit hooks -- Configure development environment +- Isso irá: + - Criar os ambientes virtuais necessários + - Instalar as dependências + - Configurar os hooks de pre-commit + - Preparar o ambiente de desenvolvimento -## 🏃‍♂️ Running Locally -Start all services using Docker Compose: +3. Configuração de ambiente + +Este projeto depende de variáveis de ambiente para o desenvolvimento local. + +Você pode configurá-las seguindo **[este guia](https://gov-hub.io/govhub/documentacao/instalacao/)**. + + +## 🏃‍♂️ Executando localmente + +Inicie todos os serviços usando Docker Compose: ```bash docker-compose up -d ``` -Access the different components: +Acesse os diferentes componentes: - Airflow: http://localhost:8080 - Jupyter: http://localhost:8888 - Superset: http://localhost:8088 -## 💻 Development +## 💻 Desenvolvimento -### Code Quality +### Qualidade de Código -This project uses several tools to maintain code quality: -- Pre-commit hooks -- Linting configurations -- Automated testing +Este projeto utiliza diversas ferramentas para manter a qualidade do código: +- Hooks de pre-commit +- Configurações de lint +- Testes automatizados -Run linting checks: +Execute a verificação de lint: ```bash make lint ``` -Run tests: +Execute os testes: ```bash make test ``` -### Project Structure +### Estrutura do projeto ``` . @@ -121,39 +129,40 @@ make test └── README.md ``` -### Makefile Commands +### Comandos do Makefile -- `make setup`: Initial project setup -- `make lint`: Run linting checks -- `make tests`: Run test suite -- `make clean`: Clean up generated files -- `make build`: Build Docker images +- `make setup`: Configuração inicial do projeto +- `make lint`: Executa verificações de lint +- `make tests`: Executa a suíte de testes +- `make clean`: Remove arquivos gerados +- `make build`: Constrói as imagens Docker -## 🔐 Git Workflow +## 🔐 Fluxo de Trabalho com Git -This project requires signed commits. To set up GPG signing: +Este projeto exige commits assinados. Para configurar a assinatura com GPG: -1. Generate a GPG key: +1. Gere uma chave GPG: ```bash gpg --full-generate-key ``` -2. Configure Git to use GPG signing: +2. Configure o Git para usar assinatura GPG: ```bash -git config --global user.signingkey YOUR_KEY_ID +git config --global user.signingkey SUA_KEY_ID git config --global commit.gpgsign true ``` -3. Add your GPG key to your GitLab account +3. Adicione sua chave GPG à sua conta do GitLab -## 📚 Documentation +## 📚 Documentação -- [Airflow Documentation](https://airflow.apache.org/docs/) -- [dbt Documentation](https://docs.getdbt.com/) -- [Superset Documentation](https://superset.apache.org/docs/intro) +- [Documentação do Airflow](https://airflow.apache.org/docs/) +- [Documentação do dbt](https://docs.getdbt.com/) +- [Documentação do Superset](https://superset.apache.org/docs/intro) +- [Documentação do GovHub](https://gov-hub.io/govhub/documentacao/instalacao/) -## 🤝 Contributing +## 🤝 Contribuição -1. Create a new branch for your feature -2. Make changes and ensure all tests pass -3. Submit a merge request +1. Crie uma nova branch para sua feature +2. Faça as alterações e garanta que todos os testes passam +3. Envie um merge request \ No newline at end of file From 4af16d78f7a6c64fb9a4f35f7c53c2ccce27879e Mon Sep 17 00:00:00 2001 From: Mateus de Castro <140627829+mat054@users.noreply.github.com> Date: Thu, 30 Apr 2026 16:55:18 -0300 Subject: [PATCH 293/317] Feat datasets virtuais (#243) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(pessoas e teds): adicionei os datasets virtuais do schema de pessoas e de teds * feat: adicionando o restante dos datasets virtuais ao dbt * feat: ajustando datasets virtuais aos dbts * feat: adicionando dt_ingest aos dbts * fix: dbt relatou problema nos schemas e comparativo entre dt ingest, fiz um cast explícito para resolver * fix: ajuste duplicação de código --- .../gold/contratos_comparativo_mensal.sql | 53 ++++-- .../contratos_dbt/gold/contratos_resumo.sql | 4 +- .../ipea/models/contratos_dbt/gold/schema.yml | 3 + .../silver/contratos_empenhos.sql | 39 +++- .../silver/contratos_faturas.sql | 8 +- .../models/contratos_dbt/silver/schema.yml | 18 ++ .../categoria_gastos_orcamento_total_.sql | 54 ++++++ .../orcamento_dbt/silver/orcamento_total_.sql | 31 +++ .../models/orcamento_dbt/silver/schema.yml | 50 +++++ ...distribuicao_raca_cor_sexo_servidores_.sql | 10 + .../models/pessoas_dbt/gold/hierarquia.sql | 91 ++++++++- .../ipea/models/pessoas_dbt/gold/schema.yml | 35 ++++ .../silver/dados_funcionais_enriquecidos.sql | 89 +++++++++ .../pessoas_dbt/silver/resumo_atividade_.sql | 15 ++ .../ipea/models/pessoas_dbt/silver/schema.yml | 52 +++++ .../categorias_execucao_financeira_teds_.sql | 64 +++++++ ...categorias_execucao_orcamentaria_teds_.sql | 79 ++++++++ .../gold/categorias_execucao_rap_teds_.sql | 31 +++ .../categorias_resumo_orcamentario_teds_.sql | 59 ++++++ ...ias_resumo_orcamentario_teds_emitente_.sql | 56 ++++++ .../gold/resumo_programa_plano_acao_.sql | 75 ++++++++ .../dbt/ipea/models/ted_dbt/gold/schema.yml | 177 ++++++++++++++++++ 22 files changed, 1070 insertions(+), 23 deletions(-) create mode 100644 airflow_lappis/dags/dbt/ipea/models/orcamento_dbt/silver/categoria_gastos_orcamento_total_.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/orcamento_dbt/silver/orcamento_total_.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/orcamento_dbt/silver/schema.yml create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_raca_cor_sexo_servidores_.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/dados_funcionais_enriquecidos.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/resumo_atividade_.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/categorias_execucao_financeira_teds_.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/categorias_execucao_orcamentaria_teds_.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/categorias_execucao_rap_teds_.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/categorias_resumo_orcamentario_teds_.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/categorias_resumo_orcamentario_teds_emitente_.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/resumo_programa_plano_acao_.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql index 3c192cd3..a1565639 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql @@ -20,26 +20,49 @@ with s.valor_pago as siafi_valor_pago, s.restos_a_pagar as siafi_restos_a_pagar, s.restos_a_pagar_pago as siafi_restos_a_pagar_pago, - c.dt_ingest as dt_ingest + greatest(c.dt_ingest::timestamptz, s.dt_ingest::timestamptz) as dt_ingest from compras_gov_data as c full join siafi_data as s using (contrato_id, mes_ref) ), - preenchimento as (select contrato_id, mes_ref from {{ ref("preenchimento_meses") }}) + preenchimento as (select contrato_id, mes_ref from {{ ref("preenchimento_meses") }}), + + contratos as ( + select id, numero, dt_ingest as dt_ingest_contratos + from {{ ref("contratos") }} + ), + + comparativo_mensal as ( + select + contrato_id, + mes_ref, + comprasgov_valor_cronograma, + comprasgov_valor_faturas, + comprasgov_saldo_contratual_disponivel, + siafi_valor_empenhado, + siafi_valor_liquidado, + siafi_valor_pago, + siafi_restos_a_pagar, + siafi_restos_a_pagar_pago, + dt_ingest + from partial_result + full join preenchimento using (contrato_id, mes_ref) + ) -- select - contrato_id, - mes_ref, - comprasgov_valor_cronograma, - comprasgov_valor_faturas, - comprasgov_saldo_contratual_disponivel, - siafi_valor_empenhado, - siafi_valor_liquidado, - siafi_valor_pago, - siafi_restos_a_pagar, - siafi_restos_a_pagar_pago, - dt_ingest -from partial_result -full join preenchimento using (contrato_id, mes_ref) + ccm.contrato_id, + ccm.mes_ref, + ccm.comprasgov_valor_cronograma, + ccm.comprasgov_valor_faturas, + ccm.comprasgov_saldo_contratual_disponivel, + ccm.siafi_valor_empenhado, + ccm.siafi_valor_liquidado, + ccm.siafi_valor_pago, + ccm.siafi_restos_a_pagar, + ccm.siafi_restos_a_pagar_pago, + c.numero, + greatest(ccm.dt_ingest::timestamptz, c.dt_ingest_contratos::timestamptz) as dt_ingest +from comparativo_mensal as ccm +left join contratos as c on ccm.contrato_id = c.id diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_resumo.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_resumo.sql index de5e03c0..e0402cef 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_resumo.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_resumo.sql @@ -40,9 +40,9 @@ select else fornecedor_tipo end as fornecedor_tipo, concat( - contratante__orgao_origem__unidade_gestora_origem__codigo, + contratante__orgao__unidade_gestora__codigo, ' - ', - contratante__orgao_origem__unidade_gestora_origem__nome_resumido + contratante__orgao__unidade_gestora__nome_resumido ) as "Unidade", case when vigencia_fim - vigencia_inicio >= 730 and num_parcelas > 1 diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/schema.yml b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/schema.yml index c8ce30b4..9f9b9587 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/schema.yml @@ -85,6 +85,9 @@ models: - name: contrato_id description: > Identificador único do contrato. + - name: numero + description: > + Número do contrato no sistema ComprasGov. - name: mes_ref description: > Mês de referência da informação financeira, preenchido para todos os meses entre o início e fim do contrato. diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_empenhos.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_empenhos.sql index 4eed67c3..79cacef9 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_empenhos.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_empenhos.sql @@ -204,7 +204,42 @@ with union select * from resultado_4 + ), + + contratos as ( + select + id, + fornecedor_nome, + unidades_requisitantes, + objeto, + vigencia_inicio, + vigencia_fim, + situacao, + dt_ingest as dt_ingest_contratos + from {{ ref("contratos") }} ) -select * -from resultado_final +select + ce.contrato_id, + ce.ne_transformed, + ce.ne_ccor, + ce.ne_info_complementar, + ce.ne_num_processo, + ce.ne_ccor_descricao, + ce.doc_observacao, + ce.natureza_despesa, + ce.natureza_despesa_descricao, + ce.ne_ccor_favorecido, + ce.ne_ccor_ano_emissao, + ce.despesas_empenhadas, + ce.despesas_liquidadas, + ce.despesas_pagas, + cc.fornecedor_nome, + cc.unidades_requisitantes, + cc.objeto, + cc.vigencia_inicio, + cc.vigencia_fim, + greatest(ce.dt_ingest, cc.dt_ingest_contratos) as dt_ingest +from resultado_final as ce +full join contratos as cc on ce.contrato_id = cc.id +where cc.situacao = 'Ativo' diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_faturas.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_faturas.sql index 60dbc949..7ca2bcff 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_faturas.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_faturas.sql @@ -7,7 +7,9 @@ with fornecedor_cnpj_cpf_idgener, processo as processo_contrato, numero as numero_contrato, - objeto as objeto_contrato + objeto as objeto_contrato, + unidades_requisitantes, + dt_ingest as dt_ingest_contratos from {{ ref("contratos") }} ), @@ -20,6 +22,7 @@ select c.processo_contrato as contrato_processo, c.fornecedor_cnpj_cpf_idgener, c.objeto_contrato, + c.unidades_requisitantes, f.tipolistafatura_id, f.justificativafatura_id, f.sfadrao_id, @@ -48,6 +51,7 @@ select f.numero_empenho, f.valor_empenho, f.subelemento, - f.dt_ingest + greatest(f.dt_ingest, c.dt_ingest_contratos) as dt_ingest from faturas_base f left join contratos c on f.contrato_id = c.contrato_id +where f.emissao < '2026-01-01' diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/schema.yml b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/schema.yml index fb496acc..9e7adfaa 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/schema.yml @@ -55,6 +55,21 @@ models: - name: despesas_pagas description: > Valor total das despesas pagas para o contrato. + - name: fornecedor_nome + description: > + Nome do fornecedor associado ao contrato. + - name: unidades_requisitantes + description: > + Unidades requisitantes associadas ao contrato. + - name: objeto + description: > + Descrição do objeto contratado. + - name: vigencia_inicio + description: > + Data de início da vigência do contrato. + - name: vigencia_fim + description: > + Data de término da vigência do contrato. - name: dt_ingest description: > Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. @@ -152,6 +167,9 @@ models: - name: objeto_contrato description: > Descrição do objeto contratado. + - name: unidades_requisitantes + description: > + Unidades requisitantes associadas ao contrato relacionado à fatura. - name: tipolistafatura_id description: > Identificador do tipo de lista de fatura. diff --git a/airflow_lappis/dags/dbt/ipea/models/orcamento_dbt/silver/categoria_gastos_orcamento_total_.sql b/airflow_lappis/dags/dbt/ipea/models/orcamento_dbt/silver/categoria_gastos_orcamento_total_.sql new file mode 100644 index 00000000..46069f5c --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/orcamento_dbt/silver/categoria_gastos_orcamento_total_.sql @@ -0,0 +1,54 @@ +WITH categorias_gastos AS ( + select + ano_exercicio, + acao_governo, + acao_governo_desc, + elemento_despesa, + elemento_despesa_desc, + COALESCE(dotacao_atualizada, 0) as valor, + 'Dotação' as categoria, + dt_ingest + from {{ ref('visao_orcamentaria_total') }} + + union all + + select + ano_exercicio, + acao_governo, + acao_governo_desc, + elemento_despesa, + elemento_despesa_desc, + COALESCE(despesas_empenhadas, 0) as valor, + 'Orçamento alocado (empenhado)' as categoria, + dt_ingest + from {{ ref('visao_orcamentaria_total') }} + + union all + + select + ano_exercicio, + acao_governo, + acao_governo_desc, + elemento_despesa, + elemento_despesa_desc, + COALESCE(despesar_a_pagar, 0) as valor, + 'Despesas programadas' as categoria, + dt_ingest + from {{ ref('visao_orcamentaria_total') }} + + union all + + select + ano_exercicio, + acao_governo, + acao_governo_desc, + elemento_despesa, + elemento_despesa_desc, + COALESCE(despesas_pagas, 0) as valor, + 'Despesas pagas' as categoria, + dt_ingest + from {{ ref('visao_orcamentaria_total') }} +) + +select * from categorias_gastos +order by valor desc diff --git a/airflow_lappis/dags/dbt/ipea/models/orcamento_dbt/silver/orcamento_total_.sql b/airflow_lappis/dags/dbt/ipea/models/orcamento_dbt/silver/orcamento_total_.sql new file mode 100644 index 00000000..3d5744df --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/orcamento_dbt/silver/orcamento_total_.sql @@ -0,0 +1,31 @@ +WITH orcamento_teds AS ( + select + SUM(credito_disponivel) + SUM(despesas_empenhadas) as orcamento, + ano_exercicio, + max(dt_ingest) as dt_ingest + from {{ ref('visao_orcamentaria_total') }} + where unidade_orcamentaria not in ('25300', '47204') + group by ano_exercicio +), + +orcamento AS ( + select + SUM(dotacao_atualizada) as orcamento, + ano_exercicio, + max(dt_ingest) as dt_ingest + from {{ ref('visao_orcamentaria_total') }} + group by ano_exercicio +), + +orcamento_total AS ( + select * from orcamento_teds + union + select * from orcamento +) + +select + ano_exercicio, + sum(orcamento) as orcamento, + max(dt_ingest) as dt_ingest +from orcamento_total +group by ano_exercicio diff --git a/airflow_lappis/dags/dbt/ipea/models/orcamento_dbt/silver/schema.yml b/airflow_lappis/dags/dbt/ipea/models/orcamento_dbt/silver/schema.yml new file mode 100644 index 00000000..fd9bf7dd --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/orcamento_dbt/silver/schema.yml @@ -0,0 +1,50 @@ +version: 2 + +models: + + - name: categoria_gasto_orcamento_total + description: > + Tabela que transforma a visão orçamentária em categorias de gastos para utilização em gráficos de barras empilhadas. + As categorias são: Dotação, Orçamento alocado (empenhado), Despesas programadas e Despesas pagas. + Os dados são derivados do modelo `visao_orcamentaria_total`. + meta: + tags: + - silver + columns: + - name: ano_exercicio + description: Ano do exercício orçamentário. + - name: acao_governo + description: Código da ação de governo. + - name: acao_governo_desc + description: Descrição da ação de governo. + - name: elemento_despesa + description: Código do elemento de despesa. + - name: elemento_despesa_desc + description: Descrição do elemento de despesa. + - name: valor + description: Valor numérico associado à categoria (com COALESCE 0 para nulos). + - name: categoria + description: > + Categoria do gasto para agrupamento no gráfico. + Valores possíveis: 'Dotação', 'Orçamento alocado (empenhado)', 'Despesas programadas', 'Despesas pagas'. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. + + - name: orcamento_total + description: > + Tabela que consolida o orçamento total por ano de exercício, combinando duas visões: + (1) orçamento das TEDs, calculado como crédito disponível + despesas empenhadas excluindo unidades específicas (25300, 47204); + (2) orçamento geral, baseado na dotação atualizada de todas as unidades. + O resultado final agrega os valores de ambas as visões por ano. + meta: + tags: + - silver + columns: + - name: ano_exercicio + description: Ano do exercício orçamentário. + - name: orcamento + description: Soma do orçamento total (TEDs + geral) para o ano de exercício. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_raca_cor_sexo_servidores_.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_raca_cor_sexo_servidores_.sql new file mode 100644 index 00000000..85e8de2e --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/distribuicao_raca_cor_sexo_servidores_.sql @@ -0,0 +1,10 @@ +-- Distribuição de servidores por raça/cor e sexo do servidor +SELECT + nome_cor, + SUM(CASE WHEN nome_sexo = 'FEMININO' THEN 1 ELSE 0 END) * -1 AS feminino, + SUM(CASE WHEN nome_sexo = 'MASCULINO' THEN 1 ELSE 0 END) AS masculino, + nome_situacao_funcional, + max(dt_ingest) as dt_ingest +FROM {{ ref("hierarquia") }} +GROUP BY nome_cor, nome_situacao_funcional +ORDER BY nome_cor diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/hierarquia.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/hierarquia.sql index 2cf041de..293cb6e4 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/hierarquia.sql +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/hierarquia.sql @@ -147,8 +147,95 @@ with from primeira_correlacao as pr left join {{ ref("dados_pessoais") }} as dp on pr.cpf_chefia_imediata = dp.cpf order by caminho_unidade, hierarquia_cargo + ), + + hierarquia_filtrada as ( + select * + from tabela_correlacao_cargos + where nome_situacao_funcional != 'ATIVO EM OUTRO ORGAO' + ), + + hierarquia_enriquecida as ( + select + ph.*, + df.dt_ingest as dt_ingest_dados_funcionais, + case + when df.modalidade_pgd is null + then 'Não participa' + when df.modalidade_pgd = 'parcial' + then 'Parcial' + when df.modalidade_pgd = 'integral' + then 'Integral' + when df.modalidade_pgd = 'presencial' + then 'Presencial' + when df.modalidade_pgd = 'no exterior' + then 'No exterior' + end as pdg, + case + when ph.nome_situacao_funcional = 'ATIVO EM OUTRO ORGAO' + then 'Ativo em outro órgão' + else ph.siglaunidade + end as unidade_exercicio + from hierarquia_filtrada as ph + inner join {{ ref("dados_funcionais") }} as df on ph.cpf = df.cpf + ), + + servidores_enriquecidos as ( + select + distinct + ph.*, + du.nome_municipio_uorg, + du.dt_ingest as dt_ingest_dados_uorg + from hierarquia_enriquecida as ph + inner join {{ ref("dados_uorg") }} as du on ph.siglaunidade = du.sigla_uorg + order by caminho_unidade, hierarquia_cargo + ), + + hierarquia_completa as ( + select distinct + se.codigo_siape, + se.codigo_siorg, + se.codigo_combinacao_siape, + se.codigo_combinacao_siorg, + se.matricula_siape, + se.cpf, + se.cpf_chefia_imediata, + se.cod_situacao_funcional, + se.nome_situacao_funcional, + se.hierarquia_cargo, + se.servidor, + se.dt_nascimento, + se.nome_sexo, + se.nome_estado_civil, + se.nome_nacionalidade, + se.nome_cor, + se.uf_nascimento, + se.nome_municipio_nascimento, + se.nome_chefia, + se.codigounidade, + se.codigounidadepai, + se.caminho_unidade, + se.ordem_grandeza, + se.nomeunidade, + se.siglaunidade, + se.nome_cargo, + se.servidores_carreira, + se.pdg, + se.unidade_exercicio, + se.nome_municipio_uorg, + sd.cod_escolaridade_principal, + sd.nome_escolaridade_principal, + sd.nome_deficiencia_fisica, + sd.nome_cargo as nome_cargo_emprego, + greatest( + se.dt_ingest, + se.dt_ingest_dados_funcionais, + se.dt_ingest_dados_uorg, + sd.dt_ingest + ) as dt_ingest + from servidores_enriquecidos as se + inner join {{ ref("servidores_detalhados") }} as sd on se.cpf = sd.cpf ) select * -from tabela_correlacao_cargos -where nome_situacao_funcional != 'ATIVO EM OUTRO ORGAO' +from hierarquia_completa diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/schema.yml b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/schema.yml index d4db042d..a444a693 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/gold/schema.yml @@ -172,6 +172,20 @@ models: description: Nome ou denominação do cargo ocupado. - name: servidores_carreira description: Indica se o servidor está em cargo de carreira ou em nomeação livre. + - name: pdg + description: Modalidade de participação do servidor no PGD, com valores padronizados para análise. + - name: unidade_exercicio + description: Unidade de exercício tratada para exibição nas análises de hierarquia. + - name: nome_municipio_uorg + description: Município da unidade organizacional associado à sigla da unidade. + - name: cod_escolaridade_principal + description: Código da principal escolaridade cadastrada para o servidor. + - name: nome_escolaridade_principal + description: Nome da principal escolaridade cadastrada para o servidor. + - name: nome_deficiencia_fisica + description: Descrição da deficiência física cadastrada para o servidor, quando houver. + - name: nome_cargo_emprego + description: Nome do cargo ou emprego do servidor conforme dados detalhados. - name: dt_ingest description: > Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. @@ -362,3 +376,24 @@ models: description: > Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. + - name: distribuicao_raca_cor_sexo + description: > + Tabela agregada que apresenta a distribuição de servidores por raça/cor e sexo. + A análise utiliza dados da tabela de hierarquia para calcular a quantidade de servidores + por gênero em cada categoria de raça/cor e situação funcional. + O campo 'feminino' é multiplicado por -1 para facilitar visualizações em pirâmide. + meta: + tags: + - gold + columns: + - name: nome_cor + description: Cor ou raça do servidor conforme autodeclaração. + - name: feminino + description: Quantidade de servidores do sexo feminino (valor negativo). + - name: masculino + description: Quantidade de servidores do sexo masculino. + - name: nome_situacao_funcional + description: Descrição da situação funcional do servidor. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/dados_funcionais_enriquecidos.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/dados_funcionais_enriquecidos.sql new file mode 100644 index 00000000..04351d5f --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/dados_funcionais_enriquecidos.sql @@ -0,0 +1,89 @@ +select distinct + df.cod_atividade_funcao, + df.cod_funcao, + df.cod_jornada, + df.cod_ocorr_ingresso_orgao, + df.cod_ocorr_ingresso_serv_publico, + df.cod_orgao, + df.cod_padrao, + df.cod_situacao_funcional, + df.cod_uorg_exercicio, + df.cod_upag, + df.cod_orgao_origem, + df.cpf_chefia_imediata, + df.dt_exercicio_no_orgao, + df.dt_fim_vale_ar, + df.dt_ingresso_funcao, + df.dt_ocorr_ingresso_orgao, + df.dt_ocorr_ingresso_serv_publico, + df.email_chefia_imediata, + df.email_institucional, + df.email_servidor, + df.ident_unica, + df.matricula_siape, + df.modalidade_pgd, + df.nome_atividade_funcao, + df.nome_chefe_uorg, + df.nome_funcao, + df.nome_jornada, + df.nome_ocorr_ingresso_orgao, + df.nome_ocorr_ingresso_serv_publico, + df.nome_orgao, + df.nome_regime_juridico, + df.nome_situacao_funcional, + df.nome_uorg_exercicio, + df.nome_upag, + df.participa_pgd, + df.percentual_ts, + df.sigla_orgao, + df.sigla_orgao_origem, + df.sigla_regime_juridico, + df.sigla_uorg_exercicio, + df.sigla_upag, + df.cpf, + df.cod_cargo, + df.cod_classe, + df.cod_ocorr_aposentadoria, + df.dt_ini_vale_ar, + df.dt_ocorr_aposentadoria, + df.nome_cargo, + df.nome_classe, + df.nome_ocorr_aposentadoria, + df.sigla_nivel_cargo, + df.tipo_vale_ar, + df.cod_ocorr_isencao_ir, + df.dt_ini_ocorr_isencao_ir, + df.nome_ocorr_isencao_ir, + df.cod_uorg_lotacao, + df.nome_uorg_lotacao, + df.sigla_uorg_lotacao, + df.dt_fim_ocorr_isencao_ir, + df.cod_ocorr_exclusao, + df.dt_ocorr_exclusao, + df.nome_ocorr_exclusao, + df.dt_uorg_lotacao, + df.cod_vale_transporte, + df.valor_vale_transporte, + df.dt_uorg_exercicio, + df.pontuacao_desempenho, + case + when df.modalidade_pgd is null + then 'Não participa' + when df.modalidade_pgd = 'parcial' + then 'Parcial' + when df.modalidade_pgd = 'integral' + then 'Integral' + when df.modalidade_pgd = 'presencial' + then 'Presencial' + when df.modalidade_pgd = 'no exterior' + then 'No exterior' + end as pdg, + case + when df.nome_situacao_funcional = 'ATIVO EM OUTRO ORGAO' + then 'Ativo em outro órgão' + else df.sigla_uorg_exercicio + end as unidade_exercicio, + du.nome_municipio_uorg, + greatest(df.dt_ingest, du.dt_ingest) as dt_ingest +from {{ ref("dados_funcionais") }} as df +inner join {{ ref("dados_uorg") }} as du on df.sigla_uorg_exercicio = du.sigla_uorg diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/resumo_atividade_.sql b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/resumo_atividade_.sql new file mode 100644 index 00000000..15fc04e0 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/resumo_atividade_.sql @@ -0,0 +1,15 @@ +with dados_funcionais_extract as ( + select + sigla_nivel_cargo as nivel, + cod_classe || '-' || cod_padrao as classe_padrao, + nome_situacao_funcional, + nome_cargo, + count(1) as qtd, + max(dt_ingest) as dt_ingest + from {{ ref('dados_funcionais') }} + where nome_situacao_funcional != 'APOSENTADO' + group by 1, 2, 3, 4 +) + +select * +from dados_funcionais_extract diff --git a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/schema.yml b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/schema.yml index 879c8e8f..d4306e28 100644 --- a/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/pessoas_dbt/silver/schema.yml @@ -4,6 +4,34 @@ models: # Silver + - name: dados_funcionais_enriquecidos + description: > + Tabela silver que enriquece os dados funcionais dos servidores com campos + padronizados de participação no PGD e informações da unidade organizacional + de exercício. + meta: + tags: + - silver + columns: + - name: cpf + description: CPF do servidor, com apenas dígitos. + - name: modalidade_pgd + description: Modalidade original de participação no Programa de Gestão e Desempenho (PGD). + - name: pdg + description: Modalidade de participação no PGD, com valores padronizados para análise. + - name: nome_situacao_funcional + description: Descrição da situação funcional do servidor. + - name: sigla_uorg_exercicio + description: Sigla da unidade de exercício. + - name: unidade_exercicio + description: Unidade de exercício tratada para exibição nas análises. + - name: nome_municipio_uorg + description: Município da unidade organizacional de exercício. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. + + - name: afastamento_consolidado description: > Tabela gold que consolida os registros de afastamento dos servidores, integrando as fontes @@ -333,3 +361,27 @@ models: description: > Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. + + + - name: resumo_atividade_ + description: > + Tabela que consolida o quantitativo de servidores ativos por nível de cargo, classe/padrão, situação funcional e nome do cargo. + Exclui os servidores com situação funcional 'APOSENTADO'. Derivada do modelo `dados_funcionais`. + meta: + tags: + - silver + columns: + - name: nivel + description: > + Sigla do nível do cargo (ex: DAS, FG, etc.). + - name: classe_padrao + description: Combinação de código de classe e padrão no formato 'COD_CLASSE-COD_PADRAO'. + - name: nome_situacao_funcional + description: Situação funcional do servidor (excluindo APOSENTADO). + - name: nome_cargo + description: Nome do cargo efetivo do servidor. + - name: qtd + description: Quantidade de servidores nessa combinação de atributos. + - name: dt_ingest + description: > + Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/categorias_execucao_financeira_teds_.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/categorias_execucao_financeira_teds_.sql new file mode 100644 index 00000000..96006e4f --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/categorias_execucao_financeira_teds_.sql @@ -0,0 +1,64 @@ +-- Transformando o resumo orçamentário em categorias para utilizar no gráfico de barras empilhadas +select + plano_acao, + num_transf, + '1.1 Orçamento recebido' as tipo, + (orcamento_recebido - orcamento_devolvido) as valor, + '1. Orçamento recebido' as categoria, + dt_ingest +from {{ ref('ted_resumo_orcamentario') }} + +union all +--------------------------------------------------------------------------------------------- +select + plano_acao, + num_transf, + '2.1 Financeiro recebido' as tipo, + (financeiro_recebido - (financeiro_devolvido + financeiro_cancelado)) as valor, + '2. Visão geral repasses financeiros' as categoria, + dt_ingest +from {{ ref('ted_resumo_orcamentario') }} + +union all + +select + plano_acao, + num_transf, + '2.2 Financeiro a receber (em relação ao orçamento)' as tipo, + (orcamento_recebido - orcamento_devolvido - (financeiro_recebido - (financeiro_devolvido + financeiro_cancelado))) as valor, + '2. Visão geral repasses financeiros' as categoria, + dt_ingest +from {{ ref('ted_resumo_orcamentario') }} + +union all + +select + plano_acao, + num_transf, + '3.1 Despesas pagas no exercício' as tipo, + despesas_pagas_exercicio as valor, + '3. Detalhe da execução financeira' as categoria, + dt_ingest +from {{ ref('ted_resumo_orcamentario') }} + +union all + +select + plano_acao, + num_transf, + '3.2 Despesas pagas RAP' as tipo, + despesas_pagas_rap as valor, + '3. Detalhe da execução financeira' as categoria, + dt_ingest +from {{ ref('ted_resumo_orcamentario') }} + +union all + +select + plano_acao, + num_transf, + '3.3 Saldo financeiro' as tipo, + (financeiro_recebido - (financeiro_devolvido + financeiro_cancelado + despesas_pagas_exercicio + despesas_pagas_rap)) as valor, + '3. Detalhe da execução financeira' as categoria, + dt_ingest +from {{ ref('ted_resumo_orcamentario') }} diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/categorias_execucao_orcamentaria_teds_.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/categorias_execucao_orcamentaria_teds_.sql new file mode 100644 index 00000000..57365577 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/categorias_execucao_orcamentaria_teds_.sql @@ -0,0 +1,79 @@ +-- Transformando o resumo orçamentário em categorias para utilizar no gráfico de barras empilhadas +select + plano_acao, + num_transf, + '1.1 Destaque total recebido' as tipo, + (orcamento_recebido - orcamento_devolvido) as valor, + '1. Destaque orçamentário' as categoria, + dt_ingest +from {{ ref('ted_resumo_orcamentario') }} + +union all + +select + plano_acao, + num_transf, + '1.2 Destaque a receber' as tipo, + (valor_firmado - orcamento_recebido + orcamento_devolvido) as valor, + '1. Destaque orçamentário' as categoria, + dt_ingest +from {{ ref('ted_resumo_orcamentario') }} + +--------------------------------------------------------------------------------------------- + +union all + +select + plano_acao, + num_transf, + '2.1 Empenhado (total-anulado)' as tipo, + (empenhado - empenho_anulado) as valor, + '2. Visão geral da exec. orçamentária' as categoria, + dt_ingest +from {{ ref('ted_resumo_orcamentario') }} + +union all + +select + plano_acao, + num_transf, + '2.2 A empenhar' as tipo, + (orcamento_recebido - orcamento_devolvido) - (empenhado - empenho_anulado) as valor, + '2. Visão geral da exec. orçamentária' as categoria, + dt_ingest +from {{ ref('ted_resumo_orcamentario') }} + +--------------------------------------------------------------------------------------------- + +union all + +select + plano_acao, + num_transf, + '3.1 Despesas pagas no exercício' as tipo, + despesas_pagas_exercicio as valor, + '3. Detalhe da exec. orçamentária' as categoria, + dt_ingest +from {{ ref('ted_resumo_orcamentario') }} + +union all + +select + plano_acao, + num_transf, + '3.2 Despesas pagas (RAP)' as tipo, + despesas_pagas_rap as valor, + '3. Detalhe da exec. orçamentária' as categoria, + dt_ingest +from {{ ref('ted_resumo_orcamentario') }} + +union all + +select + plano_acao, + num_transf, + '3.3 Saldo empenho' as tipo, + (empenhado - empenho_anulado) - (despesas_pagas_exercicio + despesas_pagas_rap) as valor, + '3. Detalhe da exec. orçamentária' as categoria, + dt_ingest +from {{ ref('ted_resumo_orcamentario') }} diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/categorias_execucao_rap_teds_.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/categorias_execucao_rap_teds_.sql new file mode 100644 index 00000000..60b26ba3 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/categorias_execucao_rap_teds_.sql @@ -0,0 +1,31 @@ +-- Transformando o resumo orçamentário em categorias para utilizar no gráfico de barras empilhadas +select + plano_acao, + num_transf, + '1.1 Restos a pagar inscritos' as tipo, + restos_a_pagar as valor, + '1. Restos a pagar inscritos' as categoria, + dt_ingest +from {{ ref('ted_resumo_orcamentario') }} + +union all + +select + plano_acao, + num_transf, + '2.1 Despesas pagas RAP' as tipo, + despesas_pagas_rap as valor, + '2. Detalhe da exec. orçamentária - Restos a Pagar' as categoria, + dt_ingest +from {{ ref('ted_resumo_orcamentario') }} + +union all + +select + plano_acao, + num_transf, + '2.2 Saldo RAP' as tipo, + restos_a_pagar - despesas_pagas_rap as valor, + '2. Detalhe da exec. orçamentária - Restos a Pagar' as categoria, + dt_ingest +from {{ ref('ted_resumo_orcamentario') }} diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/categorias_resumo_orcamentario_teds_.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/categorias_resumo_orcamentario_teds_.sql new file mode 100644 index 00000000..88f16310 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/categorias_resumo_orcamentario_teds_.sql @@ -0,0 +1,59 @@ +with + + categorias_emitente as ( + select + plano_acao, + num_transf, + case + when tipo = '2.1 Destaque total enviado' + then '2.1 Destaque total recebido' + when tipo = '2.2 Destaque a enviar' + then '2.2 Destaque a receber' + when tipo = '3.1 Financeiro enviado' + then '4.1 Financeiro recebido' + when tipo = '3.2 Financeiro a enviar (em relação ao orçamento)' + then '4.2 Financeiro a receber (em relação ao orçamento)' + else tipo + end as tipo, + valor, + case + when categoria = '3. Repasse financeiro' + then '4. Repasse financeiro' + else categoria + end as categoria, + dt_ingest + from {{ ref("categorias_resumo_orcamentario_teds_emitente_") }} + ), + + execucao_orcamentaria as ( + select + plano_acao, + num_transf, + '3.1 Total empenhado' as tipo, + (empenhado - empenho_anulado) as valor, + '3. Execução orçamentária' as categoria, + dt_ingest + from {{ ref("ted_resumo_orcamentario") }} + + union all + + select + plano_acao, + num_transf, + '3.2 A empenhar' as tipo, + ( + (orcamento_recebido - orcamento_devolvido) + - (empenhado - empenho_anulado) + ) as valor, + '3. Execução orçamentária' as categoria, + dt_ingest + from {{ ref("ted_resumo_orcamentario") }} + ) + +select * +from categorias_emitente + +union all + +select * +from execucao_orcamentaria diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/categorias_resumo_orcamentario_teds_emitente_.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/categorias_resumo_orcamentario_teds_emitente_.sql new file mode 100644 index 00000000..5bfecc37 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/categorias_resumo_orcamentario_teds_emitente_.sql @@ -0,0 +1,56 @@ +-- Transformando o resumo orçamentário em categorias para utilizar no gráfico de barras empilhadas +select + plano_acao, + num_transf, + '1.1 Valor firmado' as tipo, + valor_firmado as valor, + '1. Valor firmado' as categoria, + dt_ingest +from {{ ref('ted_resumo_orcamentario') }} + +union all + +--------------------------------------------------------------------------------------------- +select + plano_acao, + num_transf, + '2.1 Destaque total enviado' as tipo, + (orcamento_recebido - orcamento_devolvido) as valor, + '2. Destaque orçamentário' as categoria, + dt_ingest +from {{ ref('ted_resumo_orcamentario') }} + +union all + +select + plano_acao, + num_transf, + '2.2 Destaque a enviar' as tipo, + (valor_firmado - (orcamento_recebido - orcamento_devolvido)) as valor, + '2. Destaque orçamentário' as categoria, + dt_ingest +from {{ ref('ted_resumo_orcamentario') }} + +--------------------------------------------------------------------------------------------- + +union all + +select + plano_acao, + num_transf, + '3.1 Financeiro enviado' as tipo, + (financeiro_recebido - (financeiro_devolvido + financeiro_cancelado)) as valor, + '3. Repasse financeiro' as categoria, + dt_ingest +from {{ ref('ted_resumo_orcamentario') }} + +union all + +select + plano_acao, + num_transf, + '3.2 Financeiro a enviar (em relação ao orçamento)' as tipo, + (orcamento_recebido - orcamento_devolvido - (financeiro_recebido - (financeiro_devolvido + financeiro_cancelado))) as valor, + '3. Repasse financeiro' as categoria, + dt_ingest +from {{ ref('ted_resumo_orcamentario') }} diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/resumo_programa_plano_acao_.sql b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/resumo_programa_plano_acao_.sql new file mode 100644 index 00000000..f0c62bf4 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/resumo_programa_plano_acao_.sql @@ -0,0 +1,75 @@ +WITH percent_vigencia AS ( + select + planos_acao.id_plano_acao, + planos_acao.tx_objeto_plano_acao as objeto_plano_acao, + planos_acao.dt_inicio_vigencia, + planos_acao.dt_fim_vigencia, + CASE + WHEN planos_acao.dt_fim_vigencia = planos_acao.dt_inicio_vigencia THEN 100 + WHEN CURRENT_DATE < planos_acao.dt_inicio_vigencia THEN 0 + WHEN CURRENT_DATE >= planos_acao.dt_fim_vigencia THEN 1 + ELSE (ROUND( + (CURRENT_DATE - planos_acao.dt_inicio_vigencia)::numeric / + NULLIF((planos_acao.dt_fim_vigencia - planos_acao.dt_inicio_vigencia), 0) * 100, + 2) / 100 + ) + END AS percentual_conclusao, + programas.id_programa as programa, + programas.sigla_unidade_descentralizadora, + programas.sigla_unidade_responsavel_acompanhamento, + programas.tx_nome_institucional_programa as nome_institucional_programa, + planos_acao.dt_ingest as dt_ingest_pa + from {{ ref('planos_acao') }} as planos_acao + inner join {{ source('transfere_gov', 'programas') }} as programas + on planos_acao.id_programa = programas.id_programa +) + +select + ro.plano_acao, + ro.num_transf, + ro.sigla_unidade_descentralizada, + ro.ted_beneficiario_emitente, + ro.valor_firmado, + ro.orcamento_recebido, + ro.orcamento_devolvido, + ro.empenhado, + ro.empenho_anulado, + ro.despesas_pagas_exercicio, + ro.despesas_pagas_rap, + ro.restos_a_pagar, + ro.despesas_liquidada, + ro.financeiro_recebido, + ro.financeiro_devolvido, + ro.financeiro_cancelado, + pv.objeto_plano_acao, + pv.dt_inicio_vigencia, + pv.dt_fim_vigencia, + pv.percentual_conclusao, + pv.programa, + pv.sigla_unidade_descentralizadora as sigla_unidade_descentralizadora_programa, + pv.sigla_unidade_responsavel_acompanhamento, + pv.nome_institucional_programa, + CASE + WHEN ro.ted_beneficiario_emitente = 'emitente' THEN + CASE + WHEN ro.financeiro_recebido >= ro.valor_firmado THEN 1 + WHEN ro.financeiro_recebido = 0 THEN 0 + ELSE (ROUND( + (ro.financeiro_recebido / NULLIF(ro.valor_firmado, 0)) * 100, + 2) / 100 + ) + END + ELSE + CASE + WHEN ro.despesas_pagas_exercicio + ro.despesas_pagas_rap >= ro.valor_firmado THEN 1 + WHEN ro.despesas_pagas_exercicio + ro.despesas_pagas_rap = 0 THEN 0 + ELSE (ROUND( + ((ro.despesas_pagas_exercicio + ro.despesas_pagas_rap) / NULLIF(ro.valor_firmado, 0)) * 100, + 2) / 100 + ) + END + END AS percentual_conclusao_orcamentaria, + greatest(ro.dt_ingest, pv.dt_ingest_pa) as dt_ingest +from {{ ref('ted_resumo_orcamentario') }} as ro +full join percent_vigencia as pv + on ro.plano_acao = pv.id_plano_acao diff --git a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/schema.yml b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/schema.yml index 415d8375..5e7aead9 100644 --- a/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/ted_dbt/gold/schema.yml @@ -62,3 +62,180 @@ models: description: > Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. + + - name: categorias_resumo_orcamentario_ted + description: > + Tabela que transforma o resumo orçamentário em categorias (Valor firmado, Destaque orçamentário, Execução orçamentária e Repasse financeiro) para utilização em gráficos de barras empilhadas. + Os dados são derivados do modelo `ted_resumo_orcamentario`. + meta: + tags: + - gold + columns: + - name: plano_acao + description: Identificador do Plano de Ação. + - name: num_transf + description: Número da transferência. + - name: tipo + description: > + Subcategoria detalhada do valor (ex: '1.1 Valor firmado', '2.1 Destaque total recebido'). + - name: valor + description: Valor numérico associado ao tipo. + - name: categoria + description: > + Categoria principal para agrupamento no gráfico (ex: '1. Valor firmado', '2. Destaque orçamentário'). + - name: dt_ingest + description: Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. + + - name: categorias_resumo_orcamentario_ted_emitente + description: > + Tabela que transforma o resumo orçamentário em categorias (Valor firmado, Destaque orçamentário e Repasse financeiro) + sob a perspectiva do emitente, para utilização em gráficos de barras empilhadas. + Os dados são derivados do modelo `ted_resumo_orcamentario`. + meta: + tags: + - gold + columns: + - name: plano_acao + description: Identificador do Plano de Ação. + - name: num_transf + description: Número da transferência. + - name: tipo + description: > + Subcategoria detalhada do valor (ex: '1.1 Valor firmado', '2.1 Destaque total enviado'). + - name: valor + description: Valor numérico associado ao tipo. + - name: categoria + description: > + Categoria principal para agrupamento no gráfico (ex: '1. Valor firmado', '2. Destaque orçamentário', '3. Repasse financeiro'). + - name: dt_ingest + description: Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. + + - name: categorias_execucao_financeira_ted + description: > + Tabela que transforma o resumo orçamentário em categorias de execução financeira para utilização em gráficos de barras empilhadas. + Os dados são derivados do modelo `ted_resumo_orcamentario`. + meta: + tags: + - gold + columns: + - name: plano_acao + description: Identificador do Plano de Ação. + - name: num_transf + description: Número da transferência. + - name: tipo + description: > + Subcategoria detalhada do valor (ex: '2.1 Financeiro recebido', '3.3 Saldo financeiro'). + - name: valor + description: Valor numérico associado ao tipo. + - name: categoria + description: > + Categoria principal para agrupamento no gráfico (ex: '2. Visão geral repasses financeiros', '3. Detalhe da execução financeira'). + - name: dt_ingest + description: Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. + + - name: categorias_execucao_orcamentaria_ted + description: > + Tabela que transforma o resumo orçamentário em categorias de execução orçamentária para utilização em gráficos de barras empilhadas. + Os dados são derivados do modelo `ted_resumo_orcamentario`. + meta: + tags: + - gold + columns: + - name: plano_acao + description: Identificador do Plano de Ação. + - name: num_transf + description: Número da transferência. + - name: tipo + description: > + Subcategoria detalhada do valor (ex: '2.1 Empenhado (total-anulado)', '3.3 Saldo empenho'). + - name: valor + description: Valor numérico associado ao tipo. + - name: categoria + description: > + Categoria principal para agrupamento no gráfico (ex: '2. Visão geral da exec. orçamentária', '3. Detalhe da exec. orçamentária'). + - name: dt_ingest + description: Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. + + - name: categorias_execucao_rap_ted + description: > + Tabela que transforma o resumo orçamentário em categorias de execução de Restos a Pagar (RAP) para utilização em gráficos de barras empilhadas. + Os dados são derivados do modelo `ted_resumo_orcamentario`. + meta: + tags: + - gold + columns: + - name: plano_acao + description: Identificador do Plano de Ação. + - name: num_transf + description: Número da transferência. + - name: tipo + description: > + Subcategoria detalhada do valor (ex: '1.1 Restos a pagar inscritos', '2.2 Saldo RAP'). + - name: valor + description: Valor numérico associado ao tipo. + - name: categoria + description: > + Categoria principal para agrupamento no gráfico (ex: '1. Restos a pagar inscritos', '2. Detalhe da exec. orçamentária - Restos a Pagar'). + - name: dt_ingest + description: Data e hora (UTC-3 Brasília) mais recente de ingestão dos dados das tabelas fonte utilizadas neste modelo. + + - name: resumo_do_programa_plano_acao + description: > + Tabela consolidada que une o resumo orçamentário (TED) com informações de vigência e programas (Transfere.Gov). + Calcula percentuais de conclusão baseados em datas (vigência) e em valores financeiros/orçamentários. + meta: + tags: + - gold + columns: + - name: plano_acao + description: Identificador do Plano de Ação. + - name: num_transf + description: Número da transferência. + - name: sigla_unidade_descentralizada + description: Sigla da unidade descentralizada. + - name: ted_beneficiario_emitente + description: Identifica se a unidade é beneficiária ou emitente. + - name: valor_firmado + description: Valor total pactuado no Plano de Ação. + - name: orcamento_recebido + description: Valor total do orçamento recebido. + - name: orcamento_devolvido + description: Valor total do orçamento devolvido. + - name: empenhado + description: Valor total empenhado. + - name: empenho_anulado + description: Valor total de empenhos anulados. + - name: despesas_pagas_exercicio + description: Valor das despesas pagas no exercício. + - name: despesas_pagas_rap + description: Valor das despesas pagas em Restos a Pagar. + - name: restos_a_pagar + description: Valor inscrito em Restos a Pagar. + - name: despesas_liquidada + description: Valor das despesas liquidadas. + - name: financeiro_recebido + description: Valor financeiro recebido. + - name: financeiro_devolvido + description: Valor financeiro devolvido. + - name: financeiro_cancelado + description: Valor financeiro cancelado. + - name: dt_ingest + description: Data de ingestão dos dados. + - name: objeto_plano_acao + description: Descrição do objeto do Plano de Ação. + - name: dt_inicio_vigencia + description: Data de início da vigência. + - name: dt_fim_vigencia + description: Data de fim da vigência. + - name: percentual_conclusao + description: Percentual de conclusão baseado na duração da vigência (data atual vs início/fim). + - name: programa + description: Identificador do programa. + - name: sigla_unidade_descentralizada_programa + description: Sigla da unidade descentralizada do programa. + - name: sigla_unidade_responsavel_acompanhamento + description: Sigla da unidade responsável pelo acompanhamento. + - name: nome_institucional_programa + description: Nome institucional do programa. + - name: percentual_conclusao_orcamentaria + description: Percentual de execução financeira ou orçamentária em relação ao valor firmado. From 029480823e8d5d6d236ec13077e1953fe08ebe0f Mon Sep 17 00:00:00 2001 From: Davi de Aguiar Vieira <143732704+davi-aguiar-vieira@users.noreply.github.com> Date: Sun, 3 May 2026 00:32:31 -0300 Subject: [PATCH 294/317] feat: dbt relatorios bolsistas (#247) * feat: dbt relatorios bolsistas * fix: renomeia nome dos modelos --- .../sistema_sisbolsas/silver/bolsistas.sql | 127 ++++++++++++++++++ .../silver/bolsistas_diversidade.sql | 75 +++++++++++ .../sistema_sisbolsas/silver/schema.yml | 75 +++++++++++ 3 files changed, 277 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/silver/bolsistas.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/silver/bolsistas_diversidade.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/silver/schema.yml diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/silver/bolsistas.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/silver/bolsistas.sql new file mode 100644 index 00000000..9da6d530 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/silver/bolsistas.sql @@ -0,0 +1,127 @@ +with + bolsistas as ( + select + b.co_usuario, + b.co_selecao, + b.nu_bolsa, + b.co_situacao_bolsista, + case + when b.dt_inicio ~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}' + then substring(b.dt_inicio from 1 for 10)::date + end as dt_inicio, + case + when b.dt_fim ~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}' + then substring(b.dt_fim from 1 for 10)::date + end as dt_fim + from {{ ref("sisbolsas_tb_bolsista") }} as b + ), + + usuarios as ( + select + u.co_usuario, + u.ds_nome, + regexp_replace(u.ds_login, '[^0-9]', '', 'g') as ds_login_cpf + from {{ ref("sisbolsas_tb_usuario") }} as u + ), + + selecoes as ( + select s.co_selecao, s.co_chamada_publica, s.co_modalidade, s.tp_atuacao + from {{ ref("sisbolsas_tb_selecao") }} as s + ), + + chamadas as ( + select cp.co_chamada_publica, cp.co_programa, cp.co_projeto, cp.tp_moeda + from {{ ref("sisbolsas_tb_chamada_publica") }} as cp + ), + + unidade_por_chamada as ( + select + cu.co_chamada_publica, + string_agg(distinct u.ds_sigla, ' | ' order by u.ds_sigla) as unidade, + string_agg(distinct e.ds_uf, ' | ' order by e.ds_uf) as uf_unidade + from {{ ref("sisbolsas_tb_chapubli_unidade") }} as cu + left join {{ ref("sisbolsas_tb_unidade") }} as u on cu.co_unidade = u.co_unidade + left join {{ ref("sisbolsas_tb_estado") }} as e on u.co_estado = e.co_estado + group by 1 + ), + + pagamentos as ( + select + fb.co_usuario, + fb.co_fonte_financeira, + fb.vl_total_pago, + fp.nu_mes, + fp.nu_ano, + case + when fp.nu_ano ~ '^[0-9]{4}$' and fp.nu_mes ~ '^[0-9]{1,2}$' + then + to_date( + fp.nu_ano || '-' || lpad(fp.nu_mes, 2, '0') || '-01', 'YYYY-MM-DD' + ) + end as mes_referencia + from {{ ref("sisbolsas_tb_folha_bolsista") }} as fb + left join + {{ ref("sisbolsas_tb_folha_pagamento") }} as fp + on fb.co_folha_pagamento = fp.co_folha_pagamento + ), + + coordenador_por_selecao as ( + select + bc.co_selecao, + string_agg( + distinct coalesce(uc.ds_nome, bc.ds_cpf), + ' | ' + order by coalesce(uc.ds_nome, bc.ds_cpf) + ) as coordenador + from {{ ref("sisbolsas_tb_bolsa_coordenador") }} as bc + left join + usuarios as uc + on regexp_replace(bc.ds_cpf, '[^0-9]', '', 'g') = uc.ds_login_cpf + group by 1 + ) + +select + uc.unidade as unidade, + ub.ds_nome as bolsista, + proj.tituloprojeto as titulo_projeto, + prog.ds_programa as programa, + mod.ds_modalidade as modalidade, + b.dt_inicio as inicio, + b.dt_fim as termino, + uc.uf_unidade, + p.vl_total_pago as valor, + ff.ds_fonte_financeira as recurso, + coord.coordenador, + p.mes_referencia, + b.co_situacao_bolsista as situacao_bolsista, + case + when s.tp_atuacao = '1' + then 'Presencial' + when s.tp_atuacao = '2' + then 'Não presencial' + end as atividade, + case + when c.tp_moeda = '1' + then 'Real (R$)' + when c.tp_moeda is null + then null + else 'Estrangeira' + end as moeda, + case + when p.nu_mes ~ '^[0-9]{1,2}$' then p.nu_mes::integer + end as mes_referencia_numero, + case when p.nu_ano ~ '^[0-9]{4}$' then p.nu_ano::integer end as ano_referencia +from bolsistas as b +left join usuarios as ub on b.co_usuario = ub.co_usuario +left join selecoes as s on b.co_selecao = s.co_selecao +left join chamadas as c on s.co_chamada_publica = c.co_chamada_publica +left join unidade_por_chamada as uc on c.co_chamada_publica = uc.co_chamada_publica +left join {{ ref("sisbolsas_tb_programa") }} as prog on c.co_programa = prog.co_programa +left join + {{ ref("sisbolsas_tb_modalidade") }} as mod on s.co_modalidade = mod.co_modalidade +left join pagamentos as p on b.co_usuario = p.co_usuario +left join + {{ ref("sisbolsas_tb_fonte_financeira") }} as ff + on p.co_fonte_financeira = ff.co_fonte_financeira +left join coordenador_por_selecao as coord on b.co_selecao = coord.co_selecao +left join {{ ref("ipea_pro_projetos") }} as proj on proj.projetoid::text = c.co_projeto diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/silver/bolsistas_diversidade.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/silver/bolsistas_diversidade.sql new file mode 100644 index 00000000..cadcbded --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/silver/bolsistas_diversidade.sql @@ -0,0 +1,75 @@ +with + bolsistas_base as ( + select distinct b.co_usuario, b.co_selecao + from {{ ref("sisbolsas_tb_bolsista") }} as b + ), + + unidade_por_bolsista as ( + select distinct bb.co_usuario, coalesce(u.ds_sigla, 'SEM_UNIDADE') as unidade + from bolsistas_base as bb + left join {{ ref("sisbolsas_tb_selecao") }} as s on bb.co_selecao = s.co_selecao + left join + {{ ref("sisbolsas_tb_chapubli_unidade") }} as cu + on s.co_chamada_publica = cu.co_chamada_publica + left join {{ ref("sisbolsas_tb_unidade") }} as u on cu.co_unidade = u.co_unidade + ), + + demografia as ( + select + dp.co_usuario, + case when upper(trim(dp.tp_sexo)) = 'F' then 1 else 0 end as is_mulher, + case + when dp.co_etnia is null or upper(trim(dp.co_etnia)) = 'NAN' + then 0 + when split_part(trim(dp.co_etnia), '.', 1) in ('2', '4') + then 1 + else 0 + end as is_negro + from {{ ref("sisbolsas_tb_dado_pessoal") }} as dp + ), + + agregacao as ( + select + ub.unidade, + count(distinct ub.co_usuario) as total_bolsistas, + count( + distinct case when d.is_mulher = 1 then ub.co_usuario end + ) as total_mulheres, + count( + distinct case when d.is_negro = 1 then ub.co_usuario end + ) as total_negros + from unidade_por_bolsista as ub + left join demografia as d on ub.co_usuario = d.co_usuario + group by 1 + ), + + metricas as ( + select + unidade, + total_bolsistas, + total_mulheres, + total_negros, + ceil(total_bolsistas * 0.40)::integer as meta_minima_mulheres_40, + ceil(total_bolsistas * 0.30)::integer as meta_minima_negros_30, + round( + (total_mulheres::numeric / nullif(total_bolsistas, 0)) * 100, 2 + ) as percentual_mulheres, + round( + (total_negros::numeric / nullif(total_bolsistas, 0)) * 100, 2 + ) as percentual_negros + from agregacao + ) + +select + unidade, + total_bolsistas, + total_mulheres, + total_negros, + percentual_mulheres, + percentual_negros, + meta_minima_mulheres_40, + meta_minima_negros_30, + greatest(meta_minima_mulheres_40 - total_mulheres, 0) as mulheres_faltantes_meta, + greatest(meta_minima_negros_30 - total_negros, 0) as negros_faltantes_meta +from metricas +order by total_bolsistas desc, unidade asc diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/silver/schema.yml b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/silver/schema.yml new file mode 100644 index 00000000..70cbaa5b --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/silver/schema.yml @@ -0,0 +1,75 @@ +version: 2 + +models: + - name: bolsistas + description: > + Modelo silver de bolsistas do Sisbolsas. Parte de `sisbolsas_tb_bolsista` + e consolida dados de usuário, seleção/chamada, unidade/UF, projeto, + programa, modalidade, pagamentos e coordenador. + meta: + tags: + - silver + columns: + - name: unidade + description: Sigla da unidade associada à chamada pública. + - name: bolsista + description: Nome do bolsista. + - name: titulo_projeto + description: Título do projeto vinculado à chamada pública. + - name: programa + description: Programa da chamada pública. + - name: modalidade + description: Modalidade da seleção. + - name: inicio + description: Data de início da bolsa do bolsista. + - name: termino + description: Data de término da bolsa do bolsista. + - name: atividade + description: Tipo de atuação da seleção (Presencial ou Não presencial). + - name: uf_unidade + description: UF da unidade vinculada à chamada pública. + - name: valor + description: Valor pago ao bolsista na folha. + - name: moeda + description: Tipo de moeda da chamada pública. + - name: recurso + description: Fonte financeira utilizada no pagamento. + - name: coordenador + description: Coordenador(es) associado(s) à bolsa/chamada. + - name: mes_referencia + description: Mês de referência do pagamento (primeiro dia do mês). + - name: mes_referencia_numero + description: Número do mês de referência do pagamento. + - name: ano_referencia + description: Ano de referência do pagamento. + - name: situacao_bolsista + description: Situação do bolsista conforme `co_situacao_bolsista`. + + - name: bolsistas_diversidade + description: > + Indicadores de diversidade de bolsistas por unidade (sigla), com totais, + percentuais e metas mínimas de participação de mulheres (40%) e negros (30%). + meta: + tags: + - silver + columns: + - name: unidade + description: Sigla da unidade (ou `SEM_UNIDADE` quando não há vínculo). + - name: total_bolsistas + description: Total de bolsistas distintos na unidade. + - name: total_mulheres + description: Total de bolsistas mulheres na unidade. + - name: total_negros + description: Total de bolsistas negros na unidade (etnia preta ou parda). + - name: percentual_mulheres + description: Percentual de mulheres sobre o total de bolsistas da unidade. + - name: percentual_negros + description: Percentual de negros sobre o total de bolsistas da unidade. + - name: meta_minima_mulheres_40 + description: Quantidade mínima de mulheres para atingir 40% do total da unidade. + - name: meta_minima_negros_30 + description: Quantidade mínima de negros para atingir 30% do total da unidade. + - name: mulheres_faltantes_meta + description: Quantidade de mulheres faltantes para atingir a meta mínima de 40%. + - name: negros_faltantes_meta + description: Quantidade de negros faltantes para atingir a meta mínima de 30%. From 06351754c45a4175bcdff87ffbf0bfb3940d1d8d Mon Sep 17 00:00:00 2001 From: Mateus de Castro <140627829+mat054@users.noreply.github.com> Date: Sun, 3 May 2026 09:34:28 -0300 Subject: [PATCH 295/317] fix: adicionando novas colunas (#248) --- .../dags/data_ingest/sgac/projetos_sgac_ingest_dag.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/sgac/projetos_sgac_ingest_dag.py b/airflow_lappis/dags/data_ingest/sgac/projetos_sgac_ingest_dag.py index 38e1c95f..cd9e5ef8 100644 --- a/airflow_lappis/dags/data_ingest/sgac/projetos_sgac_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/sgac/projetos_sgac_ingest_dag.py @@ -98,8 +98,10 @@ 74: "corpo", 75: "fiscal_e_substituto", 76: "numero_siafi", - 77: "atribuido_a", - 78: "atribuido_a_claims" + 77: "apostilamentos", + 78: "prorrogacao_de_oficio", + 79: "atribuido_a", + 80: "atribuido_a_claims", } EMAIL_SUBJECT = "SGAC" @@ -200,4 +202,4 @@ def insert_data_to_db(**context: Dict[str, Any]) -> None: #Fluxo da DAG process_emails_task >> insert_to_db_task - \ No newline at end of file + From 837a0a2c0355811850e3b1137c61aa28c65c297f Mon Sep 17 00:00:00 2001 From: Mateushqms Date: Tue, 5 May 2026 11:33:21 -0300 Subject: [PATCH 296/317] feat: camada gold ted_resumo_orcamentario e segundo caminho em num_transf_n_plano_acao --- .../models/empenhos_ted_dbt/gold/schema.yml | 103 ++++++++++++++ .../gold/ted_resumo_orcamentario.sql | 131 ++++++++++++++++++ .../views/num_transf_n_plano_acao.sql | 32 ++++- .../models/empenhos_ted_dbt/views/schema.yml | 31 +++-- 4 files changed, 285 insertions(+), 12 deletions(-) create mode 100644 airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/gold/schema.yml create mode 100644 airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/gold/ted_resumo_orcamentario.sql diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/gold/schema.yml b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/gold/schema.yml new file mode 100644 index 00000000..1ac76e83 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/gold/schema.yml @@ -0,0 +1,103 @@ +version: 2 + +models: + + - name: ted_resumo_orcamentario + description: > + Tabela gold de consolidação orçamentária e financeira por Plano de Ação TED do MIR. + Agrega em uma única linha por (plano_acao, num_transf) os valores provenientes de + quatro fontes distintas: o valor total pactuado no plano (TransfereGov), os créditos + orçamentários descentralizados via Notas de Crédito (SIAFI), os empenhos registrados + nas Notas de Empenho (SIAFI) e os recursos financeiros liberados via Programações + Financeiras (SIAFI + TransfereGov). As quatro dimensões são combinadas via FULL JOIN + para garantir que nenhum registro seja perdido mesmo que exista em apenas uma fonte. + O campo ted_beneficiario_emitente é sempre 'nao_indicado' pois a fonte bronze + planos_acao_ted não disponibiliza essa informação para o MIR. + meta: + tags: + - gold + columns: + - name: plano_acao + description: > + Identificador único do Plano de Ação no TransfereGov. Chave primária junto com num_transf. + - name: num_transf + description: > + Número da transferência TED — o elo entre os sistemas SIAFI e TransfereGov. + É o campo que conecta NCs, NEs e PFs do SIAFI ao Plano de Ação correspondente. + - name: sigla_unidade_descentralizada + description: > + Sigla da unidade receptora dos recursos, conforme cadastrado no Plano de Ação do TransfereGov. + - name: ted_beneficiario_emitente + description: > + Indica se a unidade do Plano de Ação é beneficiária ou emitente da TED. + Sempre 'nao_indicado' para o MIR, pois a fonte bronze não disponibiliza esse campo. + - name: valor_firmado + description: > + Valor total pactuado no Plano de Ação (vl_total_plano_acao), conforme o registro + mais recente no TransfereGov. Representa o compromisso financeiro acordado. + - name: orcamento_recebido + description: > + Soma dos valores de Notas de Crédito recebidas (eventos que não são 300301/300307), + representando o crédito orçamentário efetivamente descentralizado para o MIR. + - name: orcamento_devolvido + description: > + Soma dos valores de Notas de Crédito de eventos 300301 e 300307, + representando crédito orçamentário devolvido ao órgão concedente. + - name: empenhado + description: > + Soma dos valores positivos de despesas_empenhadas — empenhos formalizados, + excluindo anulações. + - name: empenho_anulado + description: > + Soma dos valores absolutos das despesas_empenhadas negativas — empenhos + cancelados ou revertidos após a emissão. + - name: despesas_pagas_exercicio + description: > + Total de despesas pagas no exercício corrente, proveniente do campo + despesas_pagas das Notas de Empenho. + - name: despesas_pagas_rap + description: > + Total de despesas pagas via Restos a Pagar (exercícios anteriores), + proveniente de restos_a_pagar_pagos das Notas de Empenho. + - name: restos_a_pagar + description: > + Valor total inscrito como Restos a Pagar — despesas empenhadas e não + pagas até o encerramento do exercício fiscal. + - name: despesas_liquidada + description: > + Soma das despesas liquidadas — despesas com bens ou serviços + efetivamente entregues e atestados. + - name: financeiro_recebido + description: > + Soma dos valores de Programações Financeiras com ação 'TRANSFERENCIA', + representando os recursos financeiros efetivamente recebidos pelo MIR. + - name: financeiro_devolvido + description: > + Soma dos valores de Programações Financeiras com ação 'DEVOLUCAO', + representando recursos financeiros devolvidos ao concedente. + - name: financeiro_cancelado + description: > + Soma dos valores de Programações Financeiras com ação 'CANCELAMENTO', + representando programações canceladas antes de seu processamento. + - name: dt_ingest + description: > + Timestamp mais recente de ingestão entre todas as fontes que compõem o registro + (planos_acao_ted, nc_plano_acao, empenhos_por_plano_acao, pf_unificado). + Indica quando os dados foram atualizados pela última vez (UTC-3). + tests: + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.ted_resumo_orcamentario' + nome_coluna: 'plano_acao' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.ted_resumo_orcamentario' + nome_coluna: 'valor_firmado' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.ted_resumo_orcamentario' + nome_coluna: 'empenhado' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siafi_dbt.ted_resumo_orcamentario' + nome_coluna: 'dt_ingest' + tipo_esperado: 'timestamp with time zone' diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/gold/ted_resumo_orcamentario.sql b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/gold/ted_resumo_orcamentario.sql new file mode 100644 index 00000000..3e4a79cc --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/gold/ted_resumo_orcamentario.sql @@ -0,0 +1,131 @@ +{{ config(materialized="table") }} + +with + planos_acao_deduplicado as ( + select + id_plano_acao, + sq_instrumento as num_transf, + sigla_unidade_descentralizada, + vl_total_plano_acao, + dt_ingest as dt_ingest_plano_acao + from ( + select + pa.*, + row_number() over ( + partition by pa.id_plano_acao + order by pa.dt_ingest desc + ) as rn + from {{ ref("planos_acao_ted") }} pa + ) pa_filtrado + where rn = 1 + ), + + valor_firmado_tb as ( + select + id_plano_acao as plano_acao, + num_transf, + vl_total_plano_acao as valor_firmado, + sigla_unidade_descentralizada, + dt_ingest_plano_acao as dt_ingest_vf + from planos_acao_deduplicado + ), + + valores_orcamentos_tb as ( + select + id_plano_acao as plano_acao, + nc_transferencia as num_transf, + sum( + case + when nc_evento in ('300301', '300307') then 0 + else valor_celula + end + ) as orcamento_recebido, + sum( + case + when nc_evento in ('300301', '300307') then valor_celula + else 0 + end + ) as orcamento_devolvido, + max(dt_ingest) as dt_ingest_vo + from {{ ref("nc_plano_acao") }} + where ptres not in ('-9') + group by id_plano_acao, nc_transferencia + ), + + valores_empenhados_tb as ( + select + plano_acao, + num_transf, + sum( + case when despesas_empenhadas > 0 then despesas_empenhadas else 0 end + ) as empenhado, + sum( + case when despesas_empenhadas < 0 then -despesas_empenhadas else 0 end + ) as empenho_anulado, + sum(despesas_pagas) as despesas_pagas_exercicio, + sum(restos_a_pagar_pagos) as despesas_pagas_rap, + sum(restos_a_pagar_inscritos) as restos_a_pagar, + sum(despesas_liquidadas) as despesas_liquidada, + max(dt_ingest) as dt_ingest_ve + from {{ ref("empenhos_por_plano_acao") }} + group by plano_acao, num_transf + ), + + valores_financeiro_tb as ( + select + id_plano_acao as plano_acao, + pf_inscricao as num_transf, + sum( + case + when substring(pf_acao_descricao, '(\w+) ') = 'TRANSFERENCIA' + then pf_valor_linha + else 0 + end + ) as financeiro_recebido, + sum( + case + when substring(pf_acao_descricao, '(\w+) ') = 'DEVOLUCAO' + then pf_valor_linha + else 0 + end + ) as financeiro_devolvido, + sum( + case + when substring(pf_acao_descricao, '(\w+) ') = 'CANCELAMENTO' + then pf_valor_linha + else 0 + end + ) as financeiro_cancelado, + max(dt_ingest) as dt_ingest_vfin + from {{ ref("pf_unificado") }} + group by id_plano_acao, pf_inscricao + ), + + join_parcial as ( + select + *, + greatest(vo.dt_ingest_vo, ve.dt_ingest_ve, vfin.dt_ingest_vfin) as dt_ingest_jp + from valores_orcamentos_tb vo + full join valores_empenhados_tb ve using (plano_acao, num_transf) + full join valores_financeiro_tb vfin using (plano_acao, num_transf) + ) + +select + plano_acao, + num_transf, + valor_firmado, + orcamento_recebido, + orcamento_devolvido, + empenhado, + empenho_anulado, + despesas_pagas_exercicio, + despesas_pagas_rap, + restos_a_pagar, + despesas_liquidada, + financeiro_recebido, + financeiro_devolvido, + financeiro_cancelado, + greatest(vf.dt_ingest_vf, jp.dt_ingest_jp) as dt_ingest +from valor_firmado_tb vf +full join join_parcial jp using (plano_acao, num_transf) +where (plano_acao is not null) or (num_transf is not null) diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/views/num_transf_n_plano_acao.sql b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/views/num_transf_n_plano_acao.sql index 4bede748..ef212aff 100644 --- a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/views/num_transf_n_plano_acao.sql +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/views/num_transf_n_plano_acao.sql @@ -29,8 +29,38 @@ with order by case when plano_acao is not null then 1 else 2 end ) as rn from joined + ), + + via_nc as ( + select num_transf, plano_acao + from ranked + where rn = 1 + ), + + via_sq_instrumento as ( + select + sq_instrumento as num_transf, + id_plano_acao::text as plano_acao + from {{ ref("planos_acao_ted") }} + where sq_instrumento is not null + ), + + unificado as ( + select * from via_nc + union + select * from via_sq_instrumento + ), + + final as ( + select + *, + row_number() over ( + partition by num_transf + order by case when plano_acao is not null then 1 else 2 end + ) as rn + from unificado ) select num_transf, plano_acao -from ranked +from final where rn = 1 diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/views/schema.yml b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/views/schema.yml index 25f2d8bb..c661aa0a 100644 --- a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/views/schema.yml +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/views/schema.yml @@ -4,25 +4,34 @@ models: - name: num_transf_n_plano_acao description: > - View de mapeamento entre o número de transferência do SIAFI (num_transf / nc_transferencia) + View de mapeamento entre o número de transferência do SIAFI (num_transf) e o identificador do Plano de Ação do TransfereGov (plano_acao / id_plano_acao). É a ponte central que conecta os dois sistemas. - O cruzamento é feito via Notas de Crédito: o SIAFI registra a NC com nc_transferencia - e o TransfereGov registra a mesma NC com id_plano_acao. A correspondência é feita - pela chave composta (nc, ug_emitente). + O mapeamento é construído por dois caminhos complementares, depois unificados via UNION: + + 1. Via NC: cruza nc_tesouro_mir (SIAFI) com transfere_gov.notas_de_credito pela chave + composta (nc, ug_emitente). Quando a mesma Nota de Crédito aparece nos dois sistemas, + o id_plano_acao do TransfereGov é associado ao nc_transferencia do SIAFI. + + 2. Via sq_instrumento: lê planos_acao_ted diretamente, onde o campo sq_instrumento + já contém o num_transf. Captura planos que ainda não têm NC emitida ou cujas NCs + não cruzam pelo caminho 1. + + Quando o mesmo num_transf aparece nos dois caminhos com plano_acao distintos, + um row_number prioriza o registro com plano_acao não-nulo. meta: tags: - view columns: - name: num_transf description: > - Número da transferência TED conforme registrado no SIAFI (nc_transferencia). - Chave de entrada — é o identificador que NCs, NEs e PFs usam para referenciar - uma transferência específica. + Número da transferência TED conforme registrado no SIAFI (nc_transferencia) + ou no TransfereGov (sq_instrumento). Chave de entrada usada por NCs, NEs e PFs + para referenciar uma transferência específica. - name: plano_acao description: > - Identificador do Plano de Ação no TransfereGov (id_plano_acao), obtido via - cruzamento da NC entre SIAFI e TransfereGov. Pode ser nulo quando a NC do SIAFI - não possui correspondência no TransfereGov. Tipo text na view — consumidores - devem fazer cast para integer conforme necessário. + Identificador do Plano de Ação no TransfereGov (id_plano_acao). Pode ser nulo + quando o num_transf do SIAFI não possui correspondência em nenhum dos dois caminhos + de cruzamento. Tipo text na view — consumidores devem fazer cast para integer + conforme necessário. From 09f6abe9059620a0d433a873b80f2129d8eb411c Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Tue, 5 May 2026 11:33:34 -0300 Subject: [PATCH 297/317] fix: fechamento conn postgres --- airflow_lappis/helpers/postgres_helpers.py | 23 +++++----- airflow_lappis/plugins/cliente_postgres.py | 52 ++++++++++++++-------- 2 files changed, 46 insertions(+), 29 deletions(-) diff --git a/airflow_lappis/helpers/postgres_helpers.py b/airflow_lappis/helpers/postgres_helpers.py index 4e0a55d4..de35bcf0 100755 --- a/airflow_lappis/helpers/postgres_helpers.py +++ b/airflow_lappis/helpers/postgres_helpers.py @@ -6,16 +6,19 @@ def get_postgres_conn(data_base_name: str = "postgres_default") -> str: try: hook = PostgresHook(postgres_conn_id=data_base_name) conn = hook.get_conn() - schema = conn.info.dbname - logging.info( - f"[postgres_helpers] Obtained PostgreSQL connection: " - f"dbname={schema}, user={conn.info.user}," - f"host={conn.info.host}, port={conn.info.port}" - ) - return ( - f"dbname={schema} user={conn.info.user} password={conn.info.password} " - f"host={conn.info.host} port={conn.info.port}" - ) + try: + schema = conn.info.dbname + logging.info( + f"[postgres_helpers] Obtained PostgreSQL connection: " + f"dbname={schema}, user={conn.info.user}," + f"host={conn.info.host}, port={conn.info.port}" + ) + return ( + f"dbname={schema} user={conn.info.user} password={conn.info.password} " + f"host={conn.info.host} port={conn.info.port}" + ) + finally: + conn.close() except Exception as e: logging.error(f"Failed to obtain PostgreSQL connection: {e}") raise diff --git a/airflow_lappis/plugins/cliente_postgres.py b/airflow_lappis/plugins/cliente_postgres.py index 89bf5363..9e4e0ce3 100755 --- a/airflow_lappis/plugins/cliente_postgres.py +++ b/airflow_lappis/plugins/cliente_postgres.py @@ -1,4 +1,5 @@ import logging +from contextlib import contextmanager from typing import Any, Dict, List, Optional, Tuple import psycopg2 import psycopg2.extras @@ -36,6 +37,19 @@ def __init__(self, conn_str: str) -> None: f"{conn_str}" ) + @contextmanager + def _connect(self): + """Context manager that guarantees the connection is closed after use. + + psycopg2's native context manager only handles transactions + (commit/rollback) but does not close the connection. + """ + conn = psycopg2.connect(self.conn_str) + try: + yield conn + finally: + conn.close() + def create_table_if_not_exists( self, sample_data: Dict[str, Any], @@ -82,7 +96,7 @@ def _execute(connection): if conn is not None: _execute(conn) else: - with psycopg2.connect(self.conn_str) as new_conn: + with self._connect() as new_conn: _execute(new_conn) new_conn.commit() @@ -152,13 +166,13 @@ def _execute(connection): if conn is not None: _execute(conn) else: - with psycopg2.connect(self.conn_str) as new_conn: + with self._connect() as new_conn: _execute(new_conn) new_conn.commit() def execute_query(self, query: str) -> List[Tuple[Any, ...]]: logging.info(f"[cliente_postgres.py] Executing query: {query}") - with psycopg2.connect(self.conn_str) as conn: + with self._connect() as conn: with conn.cursor() as cursor: cursor.execute(query) results = cursor.fetchall() @@ -170,27 +184,27 @@ def execute_query(self, query: str) -> List[Tuple[Any, ...]]: def get_contratos_ids(self, schema: str = "compras_gov") -> List[int]: query = f"SELECT id FROM {schema}.contratos" - with psycopg2.connect(self.conn_str) as conn: + with self._connect() as conn: with conn.cursor() as cursor: cursor.execute(query) return [row[0] for row in cursor.fetchall()] def get_id_programas(self) -> List[int]: query = "SELECT id_programa FROM transfere_gov.programas" - with psycopg2.connect(self.conn_str) as conn: + with self._connect() as conn: with conn.cursor() as cursor: cursor.execute(query) return [row[0] for row in cursor.fetchall()] def get_id_planos_acao(self) -> List[int]: query = "SELECT id_plano_acao FROM transfere_gov.planos_acao" - with psycopg2.connect(self.conn_str) as conn: + with self._connect() as conn: with conn.cursor() as cursor: cursor.execute(query) return [row[0] for row in cursor.fetchall()] def drop_table_if_exists(self, table_name: str, schema: str = "raw") -> None: - with psycopg2.connect(self.conn_str) as conn: + with self._connect() as conn: with conn.cursor() as cursor: try: cursor.execute(f"DROP TABLE IF EXISTS {schema}.{table_name};") @@ -212,7 +226,7 @@ def get_programacao_financeira(self) -> List[Tuple[Any, ...]]: "SELECT tx_numero_programacao, ug_emitente_programacao " "FROM transfere_gov.programacao_financeira" ) - with psycopg2.connect(self.conn_str) as conn: + with self._connect() as conn: with conn.cursor() as cursor: cursor.execute(query) return cursor.fetchall() @@ -260,7 +274,7 @@ def _execute(connection): if conn is not None: _execute(conn) else: - with psycopg2.connect(self.conn_str) as new_conn: + with self._connect() as new_conn: _execute(new_conn) new_conn.commit() @@ -273,7 +287,7 @@ def get_nota_credito(self) -> List[Tuple[Any, ...]]: "SELECT cd_ug_emitente_nota, cd_gestao_emitente_nota, tx_numero_nota " "FROM transfere_gov.notas_de_credito" ) - with psycopg2.connect(self.conn_str) as conn: + with self._connect() as conn: with conn.cursor() as cursor: cursor.execute(query) return cursor.fetchall() @@ -297,7 +311,7 @@ def remove_duplicates( f"Executando query para remover duplicados em {schema}.{table_name}" ) - with psycopg2.connect(self.conn_str) as conn: + with self._connect() as conn: with conn.cursor() as cursor: cursor.execute(delete_query) conn.commit() @@ -333,7 +347,7 @@ def get_codigo_unidade(self) -> list[dict]: SELECT codigounidade, ordem_grandeza FROM pessoas.unidade_organizacional """ - with psycopg2.connect(self.conn_str) as conn: + with self._connect() as conn: with conn.cursor() as cursor: cursor.execute(query) rows = cursor.fetchall() @@ -344,7 +358,7 @@ def get_codigo_unidade(self) -> list[dict]: def execute_non_query(self, query: str) -> None: logging.info(f"[cliente_postgres.py] Executando non-query: {query}") - with psycopg2.connect(self.conn_str) as conn: + with self._connect() as conn: with conn.cursor() as cursor: try: cursor.execute(query) @@ -358,7 +372,7 @@ def execute_non_query(self, query: str) -> None: def get_dashboard_kpis(self) -> Dict[str, int]: query = "SELECT kpi, valor FROM pessoas.kpis_servidores" - with psycopg2.connect(self.conn_str) as conn: + with self._connect() as conn: with conn.cursor() as cursor: cursor.execute(query) return {row[0]: row[1] for row in cursor.fetchall()} @@ -370,7 +384,7 @@ def get_dashboard_genero(self) -> Dict[str, float]: ROUND(percentual_distribuicao * 100, 1) as percentual FROM pessoas.distribuicao_genero """ - with psycopg2.connect(self.conn_str) as conn: + with self._connect() as conn: with conn.cursor() as cursor: cursor.execute(query) genero_data = {} @@ -387,7 +401,7 @@ def get_dashboard_raca_cor(self) -> List[Dict[str, Any]]: FROM pessoas.distribuicao_raca_cor ORDER BY quantidade_servidores DESC """ - with psycopg2.connect(self.conn_str) as conn: + with self._connect() as conn: with conn.cursor() as cursor: cursor.execute(query) return [ @@ -402,7 +416,7 @@ def get_dashboard_situacao_funcional(self) -> List[Dict[str, Any]]: FROM pessoas.distribuicao_situacao_funcional ORDER BY quantidade_servidores DESC """ - with psycopg2.connect(self.conn_str) as conn: + with self._connect() as conn: with conn.cursor() as cursor: cursor.execute(query) return [{"label": row[0], "valor": row[1]} for row in cursor.fetchall()] @@ -417,7 +431,7 @@ def get_dashboard_mapa_uf(self) -> Dict[str, Dict[str, Any]]: FROM pessoas.distribuicao_mapa_uf ORDER BY sigla_uf """ - with psycopg2.connect(self.conn_str) as conn: + with self._connect() as conn: with conn.cursor() as cursor: cursor.execute(query) return { @@ -438,7 +452,7 @@ def get_dashboard_tabela_servidores(self, limit: int = 100) -> List[Dict[str, An ORDER BY total DESC LIMIT %s """ - with psycopg2.connect(self.conn_str) as conn: + with self._connect() as conn: with conn.cursor() as cursor: cursor.execute(query, (limit,)) return [ From a487108b8560e6a1d05e8736495d48d2e008d91f Mon Sep 17 00:00:00 2001 From: Luana Date: Tue, 5 May 2026 16:29:43 -0300 Subject: [PATCH 298/317] =?UTF-8?q?feat:=20adiciona=20camada=20silver=20SI?= =?UTF-8?q?CONV=20com=20extra=C3=A7=C3=A3o=20de=20n=C3=BAmero=20de=20trans?= =?UTF-8?q?fer=C3=AAncia=20e=20cruzamento=20emendas=20x=20conv=C3=AAnios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../siconv_dbt/silver/emendas_convenio.sql | 122 +++++++++++++ .../silver/numero_transferencia.sql | 51 ++++++ .../mir/models/siconv_dbt/silver/schema.yaml | 169 ++++++++++++++++++ 3 files changed, 342 insertions(+) create mode 100644 airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/emendas_convenio.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/numero_transferencia.sql create mode 100644 airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/schema.yaml diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/emendas_convenio.sql b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/emendas_convenio.sql new file mode 100644 index 00000000..bd624fd1 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/emendas_convenio.sql @@ -0,0 +1,122 @@ +{{ config(materialized="table") }} + +with + emendas as ( + select * + from {{ ref("numero_transferencia") }} + ), + convenio as ( + select * + from {{ ref("convenio") }} + ), + planos_acao as ( + select * + from {{ source("transfere_gov", "planos_acao") }} + ) + +select + e.emissao_mes, + e.emissao_dia, + e.codigo_programa, + e.programa, + e.codigo_acao_ajustada, + e.acao_ajustada, + e.autor_emendas_orcamento_descricao, + e.autor_emendas_orcamento_nome, + e.uf, + e.uf_descricao, + e.municipio, + e.pais, + e.ne_ccor, + e.ne_num_processo, + e.ne_info_complementar, + e.ne_ccor_descricao, + e.doc_observacao, + e.codigo_gnd, + e.gnd, + e.natureza_despesa, + e.natureza_despesa_descricao, + e.codigo_modalidade, + e.modalidade, + e.ne_ccor_favorecido, + e.ne_ccor_favorecido_descricao, + e.ne_ccor_ano_emissao, + e.ptres, + e.item_informacao, + e.item_informacao_descricao, + e.despesas_empenhadas, + e.despesas_liquidadas, + e.despesas_pagas, + e.id_autor, + e.cargo_autor, + e.autor, + e.partido, + e.uf_autor, + e.url_foto_autor, + e.email_autor, + e.url_foto_partido, + e.numero_transferencia, + e.dt_ingest, + c.nr_convenio, + c.id_proposta, + c.dia, + c.mes, + c.ano, + c.dia_assin_conv, + c.sit_convenio, + c.subsituacao_conv, + c.situacao_publicacao, + c.instrumento_ativo, + c.ind_opera_obtv, + c.nr_processo, + c.ug_emitente, + c.dia_publ_conv, + c.dia_inic_vigenc_conv, + c.dia_fim_vigenc_conv, + c.dia_fim_vigenc_original_conv, + c.dias_prest_contas, + c.dia_limite_prest_contas, + c.data_suspensiva, + c.data_retirada_suspensiva, + c.dias_clausula_suspensiva, + c.situacao_contratacao, + c.ind_assinado, + c.motivo_suspensao, + c.ind_foto, + c.qtde_convenios, + c.qtd_ta, + c.qtd_prorroga, + c.vl_global_conv, + c.vl_repasse_conv, + c.vl_contrapartida_conv, + c.vl_empenhado_conv, + c.vl_desembolsado_conv, + c.vl_saldo_reman_tesouro, + c.vl_saldo_reman_convenente, + c.vl_rendimento_aplicacao, + c.vl_ingresso_contrapartida, + c.vl_saldo_conta, + c.valor_global_original_conv, + pa.id_plano_acao, + pa.id_programa, + pa.sigla_unidade_descentralizada, + pa.unidade_descentralizada, + pa.sigla_unidade_responsavel_execucao, + pa.unidade_responsavel_execucao, + pa.vl_total_plano_acao, + pa.dt_inicio_vigencia, + pa.dt_fim_vigencia, + pa.tx_objeto_plano_acao, + pa.tx_justificativa_plano_acao, + pa.in_forma_execucao_direta, + pa.in_forma_execucao_particulares, + pa.in_forma_execucao_descentralizada, + pa.tx_situacao_plano_acao, + pa.aa_ano_plano_acao, + pa.vl_beneficiario_especifico, + pa.vl_chamamento_publico, + pa.sq_instrumento, + pa.aa_instrumento +from emendas e +left join convenio c on e.numero_transferencia = c.nr_convenio +left join planos_acao pa on e.numero_transferencia::text = pa.sq_instrumento \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/numero_transferencia.sql b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/numero_transferencia.sql new file mode 100644 index 00000000..e0a85056 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/numero_transferencia.sql @@ -0,0 +1,51 @@ +{{ config(materialized="table") }} + +with + emendas as ( + select *, + case + when ne_info_complementar ~ '^\d+$' + then ne_info_complementar::integer + when ne_ccor_descricao ~* 'CONVENIO|FOMENTO|FOMENO' + then nullif( + regexp_replace( + ne_ccor_descricao, + '.*(?:CONVENIO|FOMENTO|FOMENO)\s*(?:N[°º]?)?\s*(\d{6}).*', + '\1' + ), + ne_ccor_descricao + )::integer + when ne_ccor_descricao ~* 'TED\s*\d{6}' + then nullif( + regexp_replace( + ne_ccor_descricao, + '.*TED\s*(\d{6}).*', + '\1' + ), + ne_ccor_descricao + )::integer + when doc_observacao ~* 'CONVENIO|FOMENTO|FOMENO' + then nullif( + regexp_replace( + doc_observacao, + '.*(?:CONVENIO|FOMENTO|FOMENO)\s*(?:N[°º]?)?\s*(\d{6}).*', + '\1' + ), + doc_observacao + )::integer + when doc_observacao ~* 'TED\s*\d{6}' + then nullif( + regexp_replace( + doc_observacao, + '.*TED\s*(\d{6}).*', + '\1' + ), + doc_observacao + )::integer + else null + end as numero_transferencia + from {{ source("emendas", "emendas_partidos") }} + ) + +select * +from emendas \ No newline at end of file diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/schema.yaml b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/schema.yaml new file mode 100644 index 00000000..fce5ee0c --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/schema.yaml @@ -0,0 +1,169 @@ +version: 2 + +models: + - name: numero_transferencia + description: > + Tabela da camada silver que extrai e padroniza o número de transferência + das emendas parlamentares. A extração é feita sequencialmente a partir de + múltiplas colunas usando as seguintes regras: + 1. Se `ne_info_complementar` contém apenas dígitos, usa diretamente. + 2. Se `ne_ccor_descricao` contém CONVENIO, FOMENTO ou FOMENO, extrai 6 dígitos via regex. + 3. Se `ne_ccor_descricao` contém TED seguido de 6 dígitos, extrai via regex. + 4. Se `doc_observacao` contém CONVENIO, FOMENTO ou FOMENO, extrai 6 dígitos via regex. + 5. Se `doc_observacao` contém TED seguido de 6 dígitos, extrai via regex. + Caso nenhuma regra se aplique, `numero_transferencia` fica como NULL. + meta: + tags: + - silver + columns: + - name: emissao_mes + description: "Mês de emissão da nota de empenho." + - name: emissao_dia + description: "Data de emissão da nota de empenho." + - name: codigo_programa + description: "Código do programa de governo." + - name: programa + description: "Descrição do programa de governo." + - name: codigo_acao_ajustada + description: "Código da ação orçamentária ajustada." + - name: acao_ajustada + description: "Descrição da ação orçamentária ajustada." + - name: autor_emendas_orcamento_descricao + description: "Descrição do autor da emenda orçamentária." + - name: autor_emendas_orcamento_nome + description: "Nome do autor da emenda orçamentária." + - name: uf + description: "Sigla da UF do autor." + - name: uf_descricao + description: "Nome da UF do autor." + - name: municipio + description: "Município associado à emenda." + - name: pais + description: "País associado à emenda." + - name: ne_ccor + description: "Número completo da nota de empenho." + - name: ne_num_processo + description: "Número do processo administrativo." + - name: ne_info_complementar + description: "Informações complementares da nota de empenho." + - name: ne_ccor_descricao + description: "Descrição da nota de empenho." + - name: doc_observacao + description: "Observações do documento." + - name: codigo_gnd + description: "Código do grupo de natureza de despesa." + - name: gnd + description: "Descrição do grupo de natureza de despesa." + - name: natureza_despesa + description: "Código da natureza de despesa." + - name: natureza_despesa_descricao + description: "Descrição da natureza de despesa." + - name: codigo_modalidade + description: "Código da modalidade de aplicação." + - name: modalidade + description: "Descrição da modalidade de aplicação." + - name: ne_ccor_favorecido + description: "CNPJ ou CPF do favorecido." + - name: ne_ccor_favorecido_descricao + description: "Nome do favorecido." + - name: ne_ccor_ano_emissao + description: "Ano de emissão da nota de empenho." + - name: ptres + description: "Programa de Trabalho Resumido." + - name: item_informacao + description: "Código do item de informação." + - name: item_informacao_descricao + description: "Descrição do item de informação." + - name: despesas_empenhadas + description: "Valor total das despesas empenhadas." + - name: despesas_liquidadas + description: "Valor das despesas liquidadas." + - name: despesas_pagas + description: "Valor das despesas pagas." + - name: id_autor + description: "Identificador do autor da emenda." + - name: cargo_autor + description: "Cargo do autor da emenda." + - name: autor + description: "Nome do autor da emenda." + - name: partido + description: "Partido do autor da emenda." + - name: uf_autor + description: "UF do autor da emenda." + - name: url_foto_autor + description: "URL da foto do autor." + - name: email_autor + description: "Email do autor." + - name: url_foto_partido + description: "URL da foto do partido." + - name: numero_transferencia + description: > + Número de transferência extraído das colunas ne_info_complementar, + ne_ccor_descricao e doc_observacao via regex. Pode ser número de + convênio ou TED. NULL quando não foi possível extrair. + - name: dt_ingest + description: "Timestamp de ingestão dos dados." + tests: + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.numero_transferencia' + nome_coluna: 'numero_transferencia' + tipo_esperado: 'integer' + + - name: emendas_convenio + description: > + Tabela da camada silver que cruza emendas parlamentares com convênios do SICONV + e planos de ação do TransfereGov. Utiliza o `numero_transferencia` extraído + na tabela `numero_transferencia` para realizar o join com a tabela de convênios + pelo `nr_convenio`, e com `planos_acao` pelo `sq_instrumento` para os casos + de TEDs que não possuem convênio associado. + Do total de 191 registros, 174 (91%) têm convênio vinculado. Os 17 restantes + são TEDs, contratos ou instrumentos sem correspondência no SICONV. + meta: + tags: + - silver + columns: + - name: numero_transferencia + description: "Número de transferência extraído das emendas — convênio ou TED." + - name: nr_convenio + description: "Número do convênio do SICONV. NULL para TEDs e contratos." + - name: sit_convenio + description: "Situação atual do convênio." + - name: dia_assin_conv + description: "Data de assinatura do convênio." + - name: dia_inic_vigenc_conv + description: "Data de início da vigência do convênio." + - name: dia_fim_vigenc_conv + description: "Data de fim da vigência do convênio." + - name: vl_global_conv + description: "Valor global do convênio." + - name: vl_repasse_conv + description: "Valor de repasse do convênio." + - name: vl_contrapartida_conv + description: "Valor de contrapartida do convênio." + - name: vl_empenhado_conv + description: "Valor empenhado do convênio." + - name: vl_desembolsado_conv + description: "Valor desembolsado do convênio." + - name: sq_instrumento + description: "Número do instrumento no TransfereGov. Preenchido para TEDs." + - name: id_plano_acao + description: "Identificador do plano de ação no TransfereGov." + - name: vl_total_plano_acao + description: "Valor total do plano de ação." + - name: tx_situacao_plano_acao + description: "Situação do plano de ação." + - name: dt_ingest + description: "Timestamp de ingestão dos dados." + tests: + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.emendas_convenio' + nome_coluna: 'nr_convenio' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.emendas_convenio' + nome_coluna: 'numero_transferencia' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.emendas_convenio' + nome_coluna: 'vl_global_conv' + tipo_esperado: 'numeric' \ No newline at end of file From 9d5a56f8911ede42858fdd6fabfbab084339e173 Mon Sep 17 00:00:00 2001 From: Mateus de Castro <140627829+mat054@users.noreply.github.com> Date: Tue, 5 May 2026 22:44:46 -0300 Subject: [PATCH 299/317] Dbt bronze sgac (#262) * feat: dbt bronze sgac + macros + schema yml * fix: ajustes no dbt bronze e macros do sgac --- .../dags/dbt/ipea/macros/safe_casts.sql | 9 ++ .../dags/dbt/ipea/macros/sharepoint.sql | 57 ++++++++ .../sistema_sisbolsas/bronze/schema.yml | 132 ++++++++++++++++++ .../bronze/sgac_projetos_sgac.sql | 77 ++++++++++ .../dags/dbt/ipea/models/sources.yml | 5 + 5 files changed, 280 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/macros/sharepoint.sql create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sgac_projetos_sgac.sql diff --git a/airflow_lappis/dags/dbt/ipea/macros/safe_casts.sql b/airflow_lappis/dags/dbt/ipea/macros/safe_casts.sql index 811b6bbd..3c021e84 100644 --- a/airflow_lappis/dags/dbt/ipea/macros/safe_casts.sql +++ b/airflow_lappis/dags/dbt/ipea/macros/safe_casts.sql @@ -1,3 +1,12 @@ +{% macro safe_text(column_name) -%} + case + when {{ column_name }} is null then null + when nullif(trim({{ column_name }}::text), '') is null then null + when upper(trim({{ column_name }}::text)) = 'NAN' then null + else trim({{ column_name }}::text) + end +{%- endmacro %} + {% macro safe_bigint(column_name) -%} case when {{ column_name }} is null then null diff --git a/airflow_lappis/dags/dbt/ipea/macros/sharepoint.sql b/airflow_lappis/dags/dbt/ipea/macros/sharepoint.sql new file mode 100644 index 00000000..87e424d4 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/macros/sharepoint.sql @@ -0,0 +1,57 @@ +{% macro clean_sharepoint_html(column_name) -%} + nullif( + trim( + regexp_replace( + regexp_replace( + replace( + replace({{ safe_text(column_name) }}, ' ', ' '), + ' ', + ' ' + ), + '<[^>]*>', + ' ', + 'g' + ), + '\s+', + ' ', + 'g' + ) + ), + '' + ) +{%- endmacro %} + +{% macro extract_jsonb_key_values( + column_name, key_name, fallback_to_text=true, separator='; ' +) -%} + case + when {{ safe_text(column_name) }} like ('[' || '%') + then ( + select + string_agg( + element ->> '{{ key_name }}', + '{{ separator }}' order by ordinality + ) + from + jsonb_array_elements(({{ safe_text(column_name) }})::jsonb) + with ordinality as elements(element, ordinality) + where nullif(element ->> '{{ key_name }}', '') is not null + ) + when {{ safe_text(column_name) }} like ('{' || '%') + then ({{ safe_text(column_name) }})::jsonb ->> '{{ key_name }}' + {% if fallback_to_text %} + else {{ safe_text(column_name) }} + {% else %} + else null + {% endif %} + end +{%- endmacro %} + +{% macro sharepoint_jsonb(column_name) -%} + case + when {{ safe_text(column_name) }} like ('[' || '%') + or {{ safe_text(column_name) }} like ('{' || '%') + then ({{ safe_text(column_name) }})::jsonb + else null + end +{%- endmacro %} diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/schema.yml b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/schema.yml index 274a6d98..61cb0b33 100644 --- a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/schema.yml @@ -1,6 +1,138 @@ version: 2 models: + - name: sgac_projetos_sgac + description: > + Tabela bronze com projetos do SGAC importados do SharePoint. Padroniza + valores textuais `NaN` para nulo, converte campos de data, timestamp, + booleanos e valores financeiros, extrai rótulos legíveis de objetos e + listas JSON do SharePoint, e remove marcações HTML dos campos descritivos. + meta: + tags: + - bronze + - sgac + columns: + - name: id_interno_item + description: Identificador interno do item no SharePoint. + - name: id + description: Identificador do projeto no SharePoint. + - name: titulo + description: Título do projeto. + - name: entidades_externas + description: Entidades externas envolvidas no projeto. + - name: instrumento + description: Tipo de instrumento extraído da referência SharePoint. + - name: instrumento_id + description: Identificador do instrumento no SharePoint. + - name: diretoria_responsavel + description: Diretoria responsável extraída da referência SharePoint. + - name: diretoria_responsavel_id + description: Identificador da diretoria responsável. + - name: objeto + description: Objeto do projeto com marcações HTML removidas. + - name: data_inicio + description: Data de início do projeto. + - name: data_vencimento + description: Data de vencimento do projeto. + - name: total_de_recursos + description: Valor total de recursos do projeto. + - name: numero_do_proc + description: Número do processo associado ao projeto. + - name: coordenador + description: Nome dos coordenadores extraídos da lista SharePoint. + - name: coordenador_json + description: Estrutura JSON original dos coordenadores. + - name: coordenador_claims + description: Claims dos coordenadores em JSON. + - name: nacionalidade + description: Nacionalidade extraída da lista SharePoint. + - name: nacionalidade_id + description: Identificadores de nacionalidade em JSON. + - name: recursos_orcamentarios + description: Valor de recursos orçamentários. + - name: recursos_nao_orcamentarios + description: Valor de recursos não orçamentários. + - name: status + description: Status do projeto extraído da referência SharePoint. + - name: status_id + description: Identificador do status. + - name: eixo_tematico + description: Eixos temáticos extraídos da lista SharePoint. + - name: eixo_tematico_id + description: Identificadores de eixo temático em JSON. + - name: predecessores + description: Projetos predecessores extraídos da lista SharePoint. + - name: predecessores_id + description: Identificadores de predecessores em JSON. + - name: prioridade + description: Prioridade extraída da referência SharePoint. + - name: prioridade_id + description: Identificador da prioridade. + - name: justificativa + description: Justificativa do projeto com marcações HTML removidas. + - name: objetivo_s_ge + description: Objetivos do projeto com marcações HTML removidas. + - name: equipe_tecnica + description: Nomes da equipe técnica extraídos da lista SharePoint. + - name: equipe_tecnica_json + description: Estrutura JSON original da equipe técnica. + - name: equipe_tecnica_claims + description: Claims da equipe técnica em JSON. + - name: codigo + description: Código do projeto, quando informado. + - name: unidades_envolvidas + description: Unidades envolvidas extraídas da lista SharePoint. + - name: unidades_envolvidas_id + description: Identificadores das unidades envolvidas em JSON. + - name: historico_observa_x0 + description: Histórico ou observações com marcações HTML removidas. + - name: a_solicitacao + description: Solicitações relacionadas extraídas da lista SharePoint. + - name: a_solicitacao_id + description: Identificadores de solicitação em JSON. + - name: modificado + description: Data e hora da última modificação no SharePoint. + - name: criado + description: Data e hora de criação no SharePoint. + - name: autor + description: Nome do autor do item no SharePoint. + - name: autor_email + description: E-mail do autor do item no SharePoint. + - name: autor_json + description: Estrutura JSON original do autor. + - name: autor_claims + description: Claims do autor. + - name: editor + description: Nome do último editor do item no SharePoint. + - name: editor_email + description: E-mail do último editor do item no SharePoint. + - name: editor_json + description: Estrutura JSON original do editor. + - name: editor_claims + description: Claims do editor. + - name: link + description: Link do item no SharePoint. + - name: nome + description: Nome do item no SharePoint. + - name: termos_aditivos + description: Termos aditivos informados para o projeto. + - name: equipe + description: Campo de equipe com marcações HTML removidas. + - name: percentual_concluido + description: Percentual concluído do projeto. + - name: corpo + description: Corpo textual do item com marcações HTML removidas. + - name: fiscal_e_substituto + description: Fiscal e substituto informados para o projeto. + - name: numero_siafi + description: Número SIAFI associado ao projeto. + - name: atribuido_a + description: Pessoa à qual o item foi atribuído. + - name: atribuido_a_claims + description: Claims da pessoa atribuída. + - name: dt_ingest + description: Data e hora de ingestão do registro. + - name: ipea_pro_bolsistas description: > Tabela bronze com o cadastro de bolsistas do sistema Sisbolsas. diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sgac_projetos_sgac.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sgac_projetos_sgac.sql new file mode 100644 index 00000000..a18f08cd --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/bronze/sgac_projetos_sgac.sql @@ -0,0 +1,77 @@ +{{ config(materialized="table") }} + +with + projetos_sgac as ( + select + ({{ safe_numeric("id_interno_item", 18, 0) }})::bigint as id_interno_item, + ({{ safe_numeric("id", 18, 0) }})::bigint as id, + {{ safe_text("titulo") }} as titulo, + {{ safe_text("entidades_externas") }} as entidades_externas, + {{ extract_jsonb_key_values("instrumento", "Value") }} as instrumento, + ({{ safe_numeric("instrumento_id", 18, 0) }})::bigint as instrumento_id, + {{ extract_jsonb_key_values("diretoria_responsavel", "Value") }} + as diretoria_responsavel, + ({{ safe_numeric("diretoria_responsavel_id", 18, 0) }})::bigint + as diretoria_responsavel_id, + {{ clean_sharepoint_html("objeto") }} as objeto, + {{ safe_date("data_inicio") }} as data_inicio, + {{ safe_date("data_vencimento") }} as data_vencimento, + {{ safe_numeric("total_de_recursos", 18, 2) }} as total_de_recursos, + {{ safe_text("numero_do_proc") }} as numero_do_proc, + {{ extract_jsonb_key_values("coordenador", "DisplayName") }} as coordenador, + {{ sharepoint_jsonb("coordenador") }} as coordenador_json, + {{ sharepoint_jsonb("coordenador_claims") }} as coordenador_claims, + {{ extract_jsonb_key_values("nacionalidade", "Value") }} as nacionalidade, + {{ sharepoint_jsonb("nacionalidade_id") }} as nacionalidade_id, + {{ safe_numeric("recursos_orcament_x00", 18, 2) }} + as recursos_orcamentarios, + {{ safe_numeric("recursos_orcament_x0", 18, 2) }} + as recursos_nao_orcamentarios, + {{ extract_jsonb_key_values("status", "Value") }} as status, + ({{ safe_numeric("status_id", 18, 0) }})::bigint as status_id, + {{ extract_jsonb_key_values("eixo_tematico", "Value") }} as eixo_tematico, + {{ sharepoint_jsonb("eixo_tematico_id") }} as eixo_tematico_id, + {{ extract_jsonb_key_values("predecessores", "Value") }} as predecessores, + {{ sharepoint_jsonb("predecessores_id") }} as predecessores_id, + {{ extract_jsonb_key_values("prioridade", "Value") }} as prioridade, + ({{ safe_numeric("prioridade_id", 18, 0) }})::bigint as prioridade_id, + {{ clean_sharepoint_html("justificativa") }} as justificativa, + {{ clean_sharepoint_html("objetivo_s_ge") }} as objetivo_s_ge, + {{ extract_jsonb_key_values("equipe_tecnica", "DisplayName") }} as equipe_tecnica, + {{ sharepoint_jsonb("equipe_tecnica") }} as equipe_tecnica_json, + {{ sharepoint_jsonb("equipe_tecnica_claims") }} as equipe_tecnica_claims, + {{ safe_text("codigo") }} as codigo, + {{ extract_jsonb_key_values("unidades_envolvidas", "Value") }} + as unidades_envolvidas, + {{ sharepoint_jsonb("unidades_envolvidas_id") }} as unidades_envolvidas_id, + {{ clean_sharepoint_html("historico_observa_x0") }} as historico_observa_x0, + {{ extract_jsonb_key_values("a_solicitacao", "Value") }} as a_solicitacao, + {{ sharepoint_jsonb("a_solicitacao_id") }} as a_solicitacao_id, + {{ safe_timestamp("modificado") }} as modificado, + {{ safe_timestamp("criado") }} as criado, + {{ extract_jsonb_key_values("autor", "DisplayName") }} as autor, + {{ extract_jsonb_key_values("autor", "Email", fallback_to_text=false) }} + as autor_email, + {{ sharepoint_jsonb("autor") }} as autor_json, + {{ safe_text("autor_claims") }} as autor_claims, + {{ extract_jsonb_key_values("editor", "DisplayName") }} as editor, + {{ extract_jsonb_key_values("editor", "Email", fallback_to_text=false) }} + as editor_email, + {{ sharepoint_jsonb("editor") }} as editor_json, + {{ safe_text("editor_claims") }} as editor_claims, + {{ safe_text("link") }} as link, + {{ safe_text("nome") }} as nome, + {{ safe_text("termos_aditivos") }} as termos_aditivos, + {{ clean_sharepoint_html("equipe") }} as equipe, + {{ safe_numeric("percentual_concluido", 18, 4) }} as percentual_concluido, + {{ clean_sharepoint_html("corpo") }} as corpo, + {{ clean_sharepoint_html("fiscal_e_substituto") }} as fiscal_e_substituto, + {{ safe_text("numero_siafi") }} as numero_siafi, + {{ extract_jsonb_key_values("atribuido_a", "DisplayName") }} as atribuido_a, + {{ safe_text("atribuido_a_claims") }} as atribuido_a_claims, + {{ safe_timestamp("dt_ingest") }} as dt_ingest + from {{ source("sgac", "projetos_sgac") }} + ) + +select * +from projetos_sgac diff --git a/airflow_lappis/dags/dbt/ipea/models/sources.yml b/airflow_lappis/dags/dbt/ipea/models/sources.yml index 2aa9dded..03d4948a 100644 --- a/airflow_lappis/dags/dbt/ipea/models/sources.yml +++ b/airflow_lappis/dags/dbt/ipea/models/sources.yml @@ -120,3 +120,8 @@ sources: - name: tb_situacao_concessao - name: tb_unidade - name: tb_usuario + + - name: sgac + schema: sgac + tables: + - name: projetos_sgac From 0274e87032891d9e9d4d7fd7f70c6507d1338ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Guimar=C3=A3es=20Borges?= <131381377+lcsgborges@users.noreply.github.com> Date: Tue, 5 May 2026 23:02:33 -0300 Subject: [PATCH 300/317] feat: enhance Makefile for local Airflow setup (#260) --- Makefile | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Makefile b/Makefile index 4cb99545..0a6d0194 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,37 @@ export PYTHONPATH := $(CURDIR)/airflow_lappis export MYPYPATH := $(CURDIR):$(CURDIR)/airflow_lappis/dags:$(CURDIR)/airflow_lappis/helpers:$(CURDIR)/airflow_lappis/plugins +AIRFLOW_SERVICE ?= airflow +AIRFLOW_LOCAL_ORGAO ?= ipea +AIRFLOW_LOCAL_DB_HOST ?= postgres +AIRFLOW_LOCAL_DB_NAME ?= postgres +AIRFLOW_LOCAL_DB_USER ?= postgres +AIRFLOW_LOCAL_DB_PASSWORD ?= postgres +AIRFLOW_LOCAL_DB_PORT ?= 5432 + setup: @if ! command -v poetry >/dev/null 2>&1; then \ echo "Poetry não encontrado. Instale antes com pipx install poetry==1.8.5"; \ exit 1; \ fi + @if [ ! -f .env ]; then \ + if [ -f local.env ]; then \ + cp local.env .env; \ + echo ".env criado a partir de local.env"; \ + else \ + echo "local.env não encontrado. Crie o .env manualmente."; \ + exit 1; \ + fi; \ + fi poetry self add poetry-plugin-export || true poetry config virtualenvs.in-project false poetry lock poetry install --no-root --with dev poetry export --without-hashes --format=requirements.txt > requirements.generated.txt bash setup-git-hooks.sh + docker compose up -d --build + $(MAKE) dev + $(MAKE) dev-check format: poetry run black . @@ -31,3 +51,21 @@ lint-ci: test: poetry run pytest tests + +dev: + @docker compose ps --status running $(AIRFLOW_SERVICE) >/dev/null 2>&1 || (echo "Serviço '$(AIRFLOW_SERVICE)' não está em execução. Rode: docker compose up -d" && exit 1) + @echo "Aguardando Airflow/DB ficarem prontos..." + @docker compose exec -T $(AIRFLOW_SERVICE) sh -c 'for i in $$(seq 1 30); do airflow db init >/dev/null 2>&1 && exit 0; sleep 2; done; echo "Airflow DB não ficou pronto a tempo para inicializar."; exit 1' + @docker compose exec -T $(AIRFLOW_SERVICE) airflow variables set airflow_orgao '$(AIRFLOW_LOCAL_ORGAO)' + @docker compose exec -T $(AIRFLOW_SERVICE) airflow variables set airflow_variables '{"ipea":{"codigos_ug":[113601,113602]},"unb":{"codigos_ug":[154040]},"ibama":{"codigos_ug":[440001,440048,440050]},"mgi":{"codigos_ug":[201082]}}' + @docker compose exec -T $(AIRFLOW_SERVICE) airflow variables set dynamic_schedules '{"empenhos_tesouro_ingest_dag":{"type":"cron","value":"0 13 * * 1-6"},"nc_tesouro_ingest_dag":{"type":"cron","value":"0 13 * * 1-6"},"pf_tesouro_ingest_dag":{"type":"cron","value":"0 13 * * 1-6"},"visao_orcamentaria_ingest":{"type":"cron","value":"0 13 * * 1-6"}}' + @docker compose exec -T $(AIRFLOW_SERVICE) sh -c "printf '%s\n' '{\"postgres_default\":{\"conn_type\":\"postgres\",\"host\":\"$(AIRFLOW_LOCAL_DB_HOST)\",\"schema\":\"$(AIRFLOW_LOCAL_DB_NAME)\",\"login\":\"$(AIRFLOW_LOCAL_DB_USER)\",\"password\":\"$(AIRFLOW_LOCAL_DB_PASSWORD)\",\"port\":$(AIRFLOW_LOCAL_DB_PORT)}}' > /tmp/airflow-connections.json && airflow connections import --overwrite /tmp/airflow-connections.json && rm -f /tmp/airflow-connections.json" + @echo "Ambiente local do Airflow configurado com sucesso." + +dev-check: + @docker compose ps --status running $(AIRFLOW_SERVICE) >/dev/null 2>&1 || (echo "Serviço '$(AIRFLOW_SERVICE)' não está em execução. Rode: docker compose up -d" && exit 1) + @docker compose exec -T $(AIRFLOW_SERVICE) airflow variables get airflow_orgao >/dev/null + @docker compose exec -T $(AIRFLOW_SERVICE) airflow variables get airflow_variables >/dev/null + @docker compose exec -T $(AIRFLOW_SERVICE) airflow variables get dynamic_schedules >/dev/null + @docker compose exec -T $(AIRFLOW_SERVICE) airflow connections get postgres_default >/dev/null + @echo "Validação concluída: variables e connection do Airflow estão configuradas." From 71b576564f1606abbcf3933710c208b1dee77a34 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Wed, 6 May 2026 00:00:41 -0300 Subject: [PATCH 301/317] chore: garante fechamento de db conn nas dags Co-authored-by: Luana Carvalho <125318146+luanaa2005@users.noreply.github.com> --- .../parlamentares_controle_historico_dag.py | 72 ++++++++++++++----- .../data_ingest/siconv/sincov_ingest_dag.py | 18 +++-- 2 files changed, 63 insertions(+), 27 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/dados_abertos/parlamentares_controle_historico_dag.py b/airflow_lappis/dags/data_ingest/dados_abertos/parlamentares_controle_historico_dag.py index 9ba44929..b61051b6 100644 --- a/airflow_lappis/dags/data_ingest/dados_abertos/parlamentares_controle_historico_dag.py +++ b/airflow_lappis/dags/data_ingest/dados_abertos/parlamentares_controle_historico_dag.py @@ -16,6 +16,7 @@ # Helpers + def _table_exists(cursor, schema: str, table: str) -> bool: cursor.execute( """ @@ -30,6 +31,7 @@ def _table_exists(cursor, schema: str, table: str) -> bool: ) return bool(cursor.fetchone()[0]) + def _table_has_column(cursor, schema: str, table: str, column: str) -> bool: cursor.execute( """ @@ -45,6 +47,7 @@ def _table_has_column(cursor, schema: str, table: str, column: str) -> bool: ) return bool(cursor.fetchone()[0]) + def _fetch_historico_ids(cursor, schema: str, table: str) -> set[int]: if not _table_exists(cursor, schema, table): return set() @@ -59,25 +62,35 @@ def _fetch_historico_ids(cursor, schema: str, table: str) -> set[int]: ) return {int(row[0]) for row in cursor.fetchall()} -def _clean_existing_historico(conn_str: str, schema: str, table: str, records: list[dict]) -> None: + +def _clean_existing_historico( + conn_str: str, schema: str, table: str, records: list[dict] +) -> None: """Remove o histórico antigo para evitar duplicação antes do insert em lote.""" if not records: return - - ids = tuple(set(item["parlamentar_id"] for item in records if "parlamentar_id" in item)) + + ids = tuple( + set(item["parlamentar_id"] for item in records if "parlamentar_id" in item) + ) if not ids: return - with psycopg2.connect(conn_str) as conn: + conn = psycopg2.connect(conn_str) + try: with conn.cursor() as cursor: if _table_exists(cursor, schema, table): cursor.execute( - f"DELETE FROM {schema}.{table} WHERE parlamentar_id IN %s", - (ids,) + f"DELETE FROM {schema}.{table} WHERE parlamentar_id IN %s", (ids,) ) + conn.commit() + finally: + conn.close() + # --- + @dag( schedule_interval=get_dynamic_schedule("parlamentares_controle_historico_dag"), start_date=datetime(2025, 1, 1), @@ -95,7 +108,9 @@ def parlamentares_controle_historico_dag() -> None: @task def sync_atuais() -> dict[str, list[int]]: """Task 1: Busca parlamentares atuais da Câmara e do Senado.""" - logging.info("[parlamentares_controle_historico_dag.py] Iniciando sync de parlamentares atuais") + logging.info( + "[parlamentares_controle_historico_dag.py] Iniciando sync de parlamentares atuais" + ) cliente_deputados = ClienteDeputados() cliente_senadores = ClienteSenadores() @@ -124,7 +139,8 @@ def sync_atuais() -> dict[str, list[int]]: int(item.get("IdentificacaoParlamentar", {}).get("CodigoParlamentar")) for item in senadores if isinstance(item, dict) - and item.get("IdentificacaoParlamentar", {}).get("CodigoParlamentar") is not None + and item.get("IdentificacaoParlamentar", {}).get("CodigoParlamentar") + is not None } payload = { @@ -159,7 +175,8 @@ def state_logic(parlamentares_atuais: dict[str, list[int]]) -> list[dict[str, st now = datetime.now() - with psycopg2.connect(conn_str) as conn: + conn = psycopg2.connect(conn_str) + try: with conn.cursor() as cursor: cursor.execute(create_table_sql) @@ -167,8 +184,12 @@ def state_logic(parlamentares_atuais: dict[str, list[int]]) -> list[dict[str, st controle_vazio = cursor.fetchone()[0] == 0 if controle_vazio: - historico_camara_ids = _fetch_historico_ids(cursor, "camara_deputados", "deputados") - historico_senado_ids = _fetch_historico_ids(cursor, "senado_federal", "senadores") + historico_camara_ids = _fetch_historico_ids( + cursor, "camara_deputados", "deputados" + ) + historico_senado_ids = _fetch_historico_ids( + cursor, "senado_federal", "senadores" + ) for fonte, ids_historicos in ( ("camara", historico_camara_ids), @@ -252,7 +273,9 @@ def state_logic(parlamentares_atuais: dict[str, list[int]]) -> list[dict[str, st ) # Evita recarga inicial desnecessária em parlamentares já contidos na bronze nativa - if _table_exists(cursor, "camara_deputados", "deputados_historico") and _table_has_column( + if _table_exists( + cursor, "camara_deputados", "deputados_historico" + ) and _table_has_column( cursor, "camara_deputados", "deputados_historico", "parlamentar_id" ): cursor.execute( @@ -272,7 +295,9 @@ def state_logic(parlamentares_atuais: dict[str, list[int]]) -> list[dict[str, st (now, now), ) - if _table_exists(cursor, "senado_federal", "senadores_historico") and _table_has_column( + if _table_exists( + cursor, "senado_federal", "senadores_historico" + ) and _table_has_column( cursor, "senado_federal", "senadores_historico", "parlamentar_id" ): cursor.execute( @@ -308,6 +333,9 @@ def state_logic(parlamentares_atuais: dict[str, list[int]]) -> list[dict[str, st """ ) rows = cursor.fetchall() + conn.commit() + finally: + conn.close() candidatos = [ { @@ -329,7 +357,9 @@ def state_logic(parlamentares_atuais: dict[str, list[int]]) -> list[dict[str, st def extrair_historico(candidatos: list[dict[str, str]]) -> None: """Task 3: Extrai histórico da fonte oficial e injeta na base.""" if not candidatos: - logging.info("[parlamentares_controle_historico_dag.py] Nenhum parlamentar elegivel para historico") + logging.info( + "[parlamentares_controle_historico_dag.py] Nenhum parlamentar elegivel para historico" + ) return conn_str = get_postgres_conn("postgres_mir") @@ -387,7 +417,9 @@ def extrair_historico(candidatos: list[dict[str, str]]) -> None: ) if historico_camara: - _clean_existing_historico(conn_str, "camara_deputados", "deputados_historico", historico_camara) + _clean_existing_historico( + conn_str, "camara_deputados", "deputados_historico", historico_camara + ) db.insert_data( historico_camara, table_name="deputados_historico", @@ -395,7 +427,9 @@ def extrair_historico(candidatos: list[dict[str, str]]) -> None: ) if historico_senado: - _clean_existing_historico(conn_str, "senado_federal", "senadores_historico", historico_senado) + _clean_existing_historico( + conn_str, "senado_federal", "senadores_historico", historico_senado + ) db.insert_data( historico_senado, table_name="senadores_historico", @@ -403,7 +437,8 @@ def extrair_historico(candidatos: list[dict[str, str]]) -> None: ) if status_updates: - with psycopg2.connect(conn_str) as conn: + conn = psycopg2.connect(conn_str) + try: with conn.cursor() as cursor: psycopg2.extras.execute_batch( cursor, @@ -420,6 +455,9 @@ def extrair_historico(candidatos: list[dict[str, str]]) -> None: for status, fonte, parlamentar_id in status_updates ], ) + conn.commit() + finally: + conn.close() logging.info( f"[parlamentares_controle_historico_dag.py] Extração concluida. " diff --git a/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py b/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py index d7cc7cb3..eb80342c 100644 --- a/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/siconv/sincov_ingest_dag.py @@ -32,7 +32,6 @@ def ingerir_tabela( zip_path: str, nome_tabela: str, nome_csv: str, - postgres_conn_str: str, conflict_fields: list, primary_key: list, skip_rows: int, @@ -40,6 +39,8 @@ def ingerir_tabela( truncate_before_insert: bool = False, ) -> None: + postgres_conn_str = get_postgres_conn("postgres_mir") + logging.info(f"Iniciando ingestão da tabela {nome_tabela}") db = ClientPostgresDB(postgres_conn_str) @@ -53,8 +54,8 @@ def ingerir_tabela( tamanho_lote = 5000 total_inserido = 0 - with psycopg2.connect(postgres_conn_str) as conn: - + conn = psycopg2.connect(postgres_conn_str) + try: if truncate_before_insert: logging.info(f"Truncando tabela siconv.{nome_tabela}...") with conn.cursor() as cursor: @@ -104,6 +105,8 @@ def ingerir_tabela( total_inserido += len(lote) conn.commit() + finally: + conn.close() if total_inserido == 0: logging.warning(f"Nenhum registro processado para {nome_tabela}") @@ -124,18 +127,13 @@ def deletar_zip(zip_path: str) -> None: path_zip = baixar_siconv() - postgres_conn_str = get_postgres_conn("postgres_mir") - ultima_task = path_zip for tabela in TABELAS_SICONV: - task_atual = ingerir_tabela.override( - task_id=f"ingerir_{tabela['nome_tabela']}" - )( + task_atual = ingerir_tabela.override(task_id=f"ingerir_{tabela['nome_tabela']}")( zip_path=path_zip, nome_tabela=tabela["nome_tabela"], nome_csv=tabela["nome_csv"], - postgres_conn_str=postgres_conn_str, conflict_fields=tabela["conflict_fields"], primary_key=tabela["primary_key"], skip_rows=tabela["skip_rows"], @@ -149,4 +147,4 @@ def deletar_zip(zip_path: str) -> None: ultima_task >> deletar_zip(path_zip) -siconv_ingestao_dag() \ No newline at end of file +siconv_ingestao_dag() From 5a789f71f58af44999e1648f8bf8cd116bfd3e7d Mon Sep 17 00:00:00 2001 From: Luana Date: Wed, 6 May 2026 17:16:41 -0300 Subject: [PATCH 302/317] fix: substitui source por ref nos models da camada silver siconv --- .../dags/dbt/mir/models/siconv_dbt/silver/emendas_convenio.sql | 2 +- .../dbt/mir/models/siconv_dbt/silver/numero_transferencia.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/emendas_convenio.sql b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/emendas_convenio.sql index bd624fd1..cce0a28f 100644 --- a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/emendas_convenio.sql +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/emendas_convenio.sql @@ -11,7 +11,7 @@ with ), planos_acao as ( select * - from {{ source("transfere_gov", "planos_acao") }} + from {{ ref("planos_acao_ted") }} ) select diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/numero_transferencia.sql b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/numero_transferencia.sql index e0a85056..5eb87cb6 100644 --- a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/numero_transferencia.sql +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/numero_transferencia.sql @@ -44,7 +44,7 @@ with )::integer else null end as numero_transferencia - from {{ source("emendas", "emendas_partidos") }} + from {{ ref("emendas_partidos") }} ) select * From 39457f5395dc84ebe585f26a474a5148d83a8734 Mon Sep 17 00:00:00 2001 From: Mateushqms Date: Thu, 7 May 2026 08:31:58 -0300 Subject: [PATCH 303/317] refactor: enriquece o resumo_orcamentario com dados dos programas --- .../models/empenhos_ted_dbt/gold/schema.yml | 25 ++++++++++++++----- .../gold/ted_resumo_orcamentario.sql | 22 +++++++++++++++- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/gold/schema.yml b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/gold/schema.yml index 1ac76e83..e0fb8900 100644 --- a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/gold/schema.yml +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/gold/schema.yml @@ -11,8 +11,6 @@ models: nas Notas de Empenho (SIAFI) e os recursos financeiros liberados via Programações Financeiras (SIAFI + TransfereGov). As quatro dimensões são combinadas via FULL JOIN para garantir que nenhum registro seja perdido mesmo que exista em apenas uma fonte. - O campo ted_beneficiario_emitente é sempre 'nao_indicado' pois a fonte bronze - planos_acao_ted não disponibiliza essa informação para o MIR. meta: tags: - gold @@ -27,10 +25,6 @@ models: - name: sigla_unidade_descentralizada description: > Sigla da unidade receptora dos recursos, conforme cadastrado no Plano de Ação do TransfereGov. - - name: ted_beneficiario_emitente - description: > - Indica se a unidade do Plano de Ação é beneficiária ou emitente da TED. - Sempre 'nao_indicado' para o MIR, pois a fonte bronze não disponibiliza esse campo. - name: valor_firmado description: > Valor total pactuado no Plano de Ação (vl_total_plano_acao), conforme o registro @@ -84,6 +78,25 @@ models: Timestamp mais recente de ingestão entre todas as fontes que compõem o registro (planos_acao_ted, nc_plano_acao, empenhos_por_plano_acao, pf_unificado). Indica quando os dados foram atualizados pela última vez (UTC-3). + - name: sigla_unidade_responsavel_acompanhamento + description: > + Sigla da unidade responsável pelo acompanhamento do programa, proveniente + de programas_ted (TransfereGov). + - name: tx_nome_institucional_programa + description: > + Nome institucional do programa ao qual o Plano de Ação está vinculado, + proveniente de programas_ted (TransfereGov). + - name: tx_objetivo_programa + description: > + Descrição do objetivo do programa, proveniente de programas_ted (TransfereGov). + - name: programa_governo + description: > + Código do programa de governo conforme classificação orçamentária SIAFI, + proveniente de nc_plano_acao. + - name: programa_governo_descricao + description: > + Descrição do programa de governo conforme classificação orçamentária SIAFI, + proveniente de nc_plano_acao. tests: - verificacao_tipagem: nome_tabela: 'siafi_dbt.ted_resumo_orcamentario' diff --git a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/gold/ted_resumo_orcamentario.sql b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/gold/ted_resumo_orcamentario.sql index 3e4a79cc..eeb0bf19 100644 --- a/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/gold/ted_resumo_orcamentario.sql +++ b/airflow_lappis/dags/dbt/mir/models/empenhos_ted_dbt/gold/ted_resumo_orcamentario.sql @@ -4,6 +4,7 @@ with planos_acao_deduplicado as ( select id_plano_acao, + id_programa, sq_instrumento as num_transf, sigla_unidade_descentralizada, vl_total_plano_acao, @@ -20,6 +21,16 @@ with where rn = 1 ), + programas_tb as ( + select + pad.id_plano_acao, + prog.sigla_unidade_responsavel_acompanhamento, + prog.tx_nome_institucional_programa, + prog.tx_objetivo_programa + from planos_acao_deduplicado pad + left join {{ ref("programas_ted") }} prog using (id_programa) + ), + valor_firmado_tb as ( select id_plano_acao as plano_acao, @@ -46,6 +57,8 @@ with else 0 end ) as orcamento_devolvido, + max(programa_governo) as programa_governo, + max(programa_governo_descricao) as programa_governo_descricao, max(dt_ingest) as dt_ingest_vo from {{ ref("nc_plano_acao") }} where ptres not in ('-9') @@ -113,6 +126,7 @@ with select plano_acao, num_transf, + sigla_unidade_descentralizada, valor_firmado, orcamento_recebido, orcamento_devolvido, @@ -125,7 +139,13 @@ select financeiro_recebido, financeiro_devolvido, financeiro_cancelado, - greatest(vf.dt_ingest_vf, jp.dt_ingest_jp) as dt_ingest + greatest(vf.dt_ingest_vf, jp.dt_ingest_jp) as dt_ingest, + prog.sigla_unidade_responsavel_acompanhamento, + prog.tx_nome_institucional_programa, + prog.tx_objetivo_programa, + jp.programa_governo, + jp.programa_governo_descricao from valor_firmado_tb vf full join join_parcial jp using (plano_acao, num_transf) +left join programas_tb prog on plano_acao = prog.id_plano_acao where (plano_acao is not null) or (num_transf is not null) From 130abc1c84110821202267b385914b299f3b2f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Let=C3=ADcia=20Hladczuk?= <109438911+HladczukLe@users.noreply.github.com> Date: Thu, 7 May 2026 19:34:37 -0300 Subject: [PATCH 304/317] =?UTF-8?q?feat:=20Implementa=20extra=C3=A7=C3=A3o?= =?UTF-8?q?=20via=20FTP,=20flattening=20de=20planilhas=20din=C3=A2micas=20?= =?UTF-8?q?e=20armazenamento=20no=20schema=20censo=5Fdemografico.=20(#241)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Implementa extração via FTP, flattening de planilhas dinâmicas e armazenamento no schema censo_demografico. Co-authored-by: Rafael Matuda * feat: Finaliza pipeline de ingestão e flattening do Censo Demográfico Co-authored-by: Rafael Matuda * fix: Resolve SonarQube warnings in cliente_ibge e mulheres_ingest_dag; Remove o openpyxl dos requirements Co-authored-by: Rafael Matuda * fix: Resolve SonarQube warnings in cliente_ibge e mulheres_ingest_dag Co-authored-by: Rafael Matuda * fix: reverter para FTP simples no cliente_ibge.py Co-authored-by: Rafael Matuda * fix: reverter para FTP simples no cliente_ibge.py Co-authored-by: Rafael Matuda * fix: corrige sintaxe da flag #NOSONAR Co-authored-by: Rafael Matuda * fix: Revertendo credenciais do FTP; Removendo modelos do DBT Co-authored-by: Rafael Matuda * feat: Adiciona openpyxl nos requirements Co-authored-by: Rafael Matuda --------- Co-authored-by: Rafael Matuda --- .../data_ingest/ibge/mulheres_ingest_dag.py | 398 ++++++++++++++++++ .../dags/dbt/ipea/models/sources.yml | 31 ++ airflow_lappis/plugins/cliente_ibge.py | 74 ++++ pyproject.toml | 1 + requirements.txt | 3 +- 5 files changed, 506 insertions(+), 1 deletion(-) create mode 100644 airflow_lappis/dags/data_ingest/ibge/mulheres_ingest_dag.py create mode 100644 airflow_lappis/plugins/cliente_ibge.py diff --git a/airflow_lappis/dags/data_ingest/ibge/mulheres_ingest_dag.py b/airflow_lappis/dags/data_ingest/ibge/mulheres_ingest_dag.py new file mode 100644 index 00000000..19c46a24 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/ibge/mulheres_ingest_dag.py @@ -0,0 +1,398 @@ +import logging +import re +import unicodedata +from datetime import datetime, timedelta + +import pandas as pd +import yaml +from airflow.decorators import dag, task +from airflow.models import Variable + +from cliente_ibge import ClienteIBGE +from cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn +from schedule_loader import get_dynamic_schedule + +# Constantes +CONECTIVOS = frozenset( + {"da", "das", "de", "do", "em", "e", "na", "no", "para", "ou", "com", "x", "que", "o"} +) + +REGRAS_CORTE_TABELAS: dict[str, int] = { + "tabela_3": 10, + "tabela_7": 6, + "tabela_9": 7, +} + +MAX_COL_LEN = 63 +VALORES_NULOS = ("nan", "none", "") + + +# Helpers: encurtar_nome_coluna +def _reordenar_prefixo_numerico(partes: list[str]) -> list[str]: + """Move um prefixo numérico (ex: '12_a_14') para o final da lista.""" + if not partes or not partes[0] or not partes[0][0].isdigit(): + return partes + + idx_fim = 0 + for i, parte in enumerate(partes): + if parte and (parte[0].isdigit() or parte in ("a", "x")): + idx_fim = i + 1 + else: + break + + if 0 < idx_fim < len(partes): + return partes[idx_fim:] + partes[:idx_fim] + return partes + + +def _remover_conectivos(partes: list[str]) -> list[str]: + """Remove conectivos e partes vazias da lista.""" + filtradas = [p for p in partes if p and p.lower() not in CONECTIVOS] + return filtradas or partes + + +def _aplicar_corte_tabela(partes: list[str], num_tabela: str) -> list[str]: + """Remove prefixo fixo de partes para tabelas com regra especial.""" + tabela_key = num_tabela.lower() + corte = REGRAS_CORTE_TABELAS.get(tabela_key) + + if corte is None or len(partes) <= 7: + return partes + + cortadas = partes[corte:] + logging.info( + "[encurtar_nome_coluna] '%s' longo para %s — removendo %d partes iniciais", + "_".join(partes), + tabela_key, + corte, + ) + return cortadas if "_".join(cortadas) else partes + + +def _abreviar_partes_meio(partes: list[str]) -> str: + """Abrevia partes do meio (exceto primeira e última) para caber em max_len.""" + if len(partes) <= 2: + return "_".join(partes) + + meio_abreviado = [p[:5] if len(p) > 6 else p for p in partes[1:-1]] + nome = "_".join([partes[0]] + meio_abreviado + [partes[-1]]) + + logging.info("[encurtar_nome_coluna] Nome abreviado: %s", nome) + return nome + + +def _truncar_preservando_ultima(nome: str, ultima: str, max_len: int) -> str: + """Último recurso: trunca preservando a última palavra.""" + if ultima: + espaco = max_len - len(ultima) - 1 + if espaco > 0: + return f"{nome[:espaco]}_{ultima}"[:max_len] + return nome[:max_len] + + +def encurtar_nome_coluna( + nome: str, + max_len: int = MAX_COL_LEN, + num_tabela: str | None = None, +) -> str: + """ + Limpa e encurta o nome da coluna: + - Remove conectivos. + - Se iniciar com número, move o prefixo numérico para o final. + - Aplica regra de corte específica por tabela quando necessário. + - Abrevia partes do meio mantendo primeira e última palavra. + - Em último caso, trunca preservando a última palavra. + """ + partes = _reordenar_prefixo_numerico(nome.split("_")) + partes = _remover_conectivos(partes) + + nome_limpo = "_".join(partes) + if len(nome_limpo) <= max_len: + return nome_limpo + + if num_tabela: + partes = _aplicar_corte_tabela(partes, num_tabela) + nome_limpo = "_".join(partes) + if len(nome_limpo) <= max_len: + return nome_limpo + + nome_abreviado = _abreviar_partes_meio(partes) + if len(nome_abreviado) <= max_len: + return nome_abreviado + + return _truncar_preservando_ultima( + nome_abreviado, partes[-1] if partes else "", max_len + ) + + +# Helpers: normalização de texto e nomes de tabela +def _remover_acentos(texto: str) -> str: + return "".join( + c for c in unicodedata.normalize("NFD", texto) if unicodedata.category(c) != "Mn" + ) + + +def _normalizar_nome_coluna( + col: str, idx: int, num_tabela: str | None, table_name: str +) -> str: + """Limpa, normaliza e encurta o nome de uma coluna.""" + sem_acento = _remover_acentos(str(col)) + limpo = re.sub( + r"[^\w%]", + "", + sem_acento.lower() + .replace("%", "_porcentagem") + .replace(" ", "_") + .replace("-", "_"), + ) + encurtado = encurtar_nome_coluna(limpo, num_tabela=num_tabela) + return encurtado if encurtado != "none" else f"coluna_vazia_{idx}" + + +def _deduplicar_colunas(colunas: list[str], max_len: int = MAX_COL_LEN) -> list[str]: + """Garante unicidade adicionando sufixo numérico às colunas duplicadas.""" + contagem: dict[str, int] = {} + resultado: list[str] = [] + + for col in colunas: + if col not in contagem: + contagem[col] = 0 + resultado.append(col) + continue + + contagem[col] += 1 + sufixo = f"_{contagem[col]}" + novo = ( + f"{col[:max_len - len(sufixo)]}{sufixo}" + if len(col) + len(sufixo) > max_len + else f"{col}{sufixo}" + ) + resultado.append(novo) + + return resultado + + +def _construir_nome_tabela( + arquivo: str, sheet_name: str, tema_ibge: str, sufixo: str +) -> str: + """Deriva o nome da tabela de destino a partir dos metadados do arquivo.""" + clean_file = arquivo.split(".")[0].lower() + match = re.search(r"(tabela_\d+)", clean_file) + short_file = match.group(1) if match else clean_file[:15] + + clean_sheet = re.sub( + r"[^\w]", + "", + _remover_acentos(sheet_name).lower().replace(" ", "_").replace("-", "_"), + ) + prefixo = tema_ibge.lower().replace(" ", "_") + return f"{prefixo}_{short_file}_{clean_sheet}{sufixo}" + + +def _obter_tema_ibge() -> str: + config_str = Variable.get("ibge_censo_config", default_var='{"database": "Mulheres"}') + return yaml.safe_load(config_str).get("database", "Mulheres") + + +# Helpers: extração do Excel +def _identificar_chunks_horizontais(df_aba: pd.DataFrame) -> list[pd.DataFrame]: + """Divide o DataFrame pelas colunas totalmente vazias (separadores).""" + cols_vazias = [ + i for i, col in enumerate(df_aba.columns) if df_aba[col].isnull().all() + ] + pontos = [-1] + cols_vazias + [len(df_aba.columns)] + + chunks = [] + for i in range(len(pontos) - 1): + chunk = df_aba.iloc[:, pontos[i] + 1 : pontos[i + 1]].copy() + chunk = chunk.dropna(axis=1, how="all").dropna(axis=0, how="all") + if not chunk.empty and len(chunk.columns) > 1: + chunks.append(chunk.reset_index(drop=True)) + return chunks + + +def _extrair_nome_coluna_cabecalho(linhas_cab: pd.DataFrame, col_idx: int) -> str: + """Constrói o nome de uma coluna a partir de múltiplas linhas de cabeçalho.""" + pedacos = [] + for row_idx in range(len(linhas_cab)): + val = str(linhas_cab.iloc[row_idx, col_idx]).strip() + unicos = linhas_cab.iloc[row_idx].dropna().unique() + if len(unicos) > 1 and val.lower() not in VALORES_NULOS: + pedacos.append(val.split(" - ")[-1].strip()) + return "_".join(pedacos) if pedacos else f"coluna_vazia_{col_idx}" + + +def _construir_cabecalho(df_raw: pd.DataFrame, idx_dados: int) -> pd.DataFrame: + """Retorna as linhas de cabeçalho, descartando a primeira se for muito longa.""" + cabecalho = df_raw.iloc[:idx_dados].copy().ffill(axis=1) + primeira_linha = " ".join( + str(v).strip() + for v in cabecalho.iloc[0].tolist() + if str(v).strip().lower() not in VALORES_NULOS + ) + return cabecalho.iloc[1:] if len(primeira_linha) > 80 else cabecalho + + +def _processar_chunk_excel( + df_raw: pd.DataFrame, + idx: int, + total: int, + sheet_name: str, + arquivo: str, + tema_ibge: str, +) -> dict | None: + """Processa um chunk horizontal do Excel e devolve o dict de metadados ou None.""" + mascara_num = df_raw.apply( + lambda r: pd.to_numeric(r, errors="coerce").notna().sum() > 1, axis=1 + ) + if not mascara_num.any(): + return None + + idx_dados = mascara_num.idxmax() + cabecalho = _construir_cabecalho(df_raw, idx_dados) + nomes = [ + _extrair_nome_coluna_cabecalho(cabecalho, i) for i in range(len(df_raw.columns)) + ] + + df = df_raw.iloc[idx_dados:].copy() + df.columns = nomes + + col_dim = df.columns[0] + df = df.dropna(subset=[col_dim]) + df = df[~df[col_dim].astype(str).str.contains("Fonte:|Nota:", case=False, na=False)] + + return { + "df": df, + "sheet_name": sheet_name, + "arquivo": arquivo, + "sufixo": f"_parte_{idx + 1}" if total > 1 else "", + "tema_ibge": tema_ibge, + } + + +# Helpers: inserção no banco +def _processar_chunk_insercao( + chunk_info: dict, db: ClientPostgresDB, schema: str +) -> str | None: + """Limpa, deduplica e insere um chunk no banco. Retorna o nome da tabela ou None.""" + df: pd.DataFrame = chunk_info["df"] + arquivo: str = chunk_info["arquivo"] + sheet_name: str = chunk_info["sheet_name"] + sufixo: str = chunk_info["sufixo"] + tema_ibge: str = chunk_info["tema_ibge"] + + num_tabela_match = re.search(r"tabela[_\- ]?\d+", arquivo, re.IGNORECASE) + num_tabela = num_tabela_match.group(0) if num_tabela_match else None + + table_name = _construir_nome_tabela(arquivo, sheet_name, tema_ibge, sufixo) + + colunas = [ + _normalizar_nome_coluna(c, idx, num_tabela, table_name) + for idx, c in enumerate(df.columns) + ] + df.columns = _deduplicar_colunas(colunas) + + colunas_fantasma = [c for c in df.columns if c.startswith("coluna_vazia")] + if colunas_fantasma: + logging.info("Removendo colunas fantasmas: %s", colunas_fantasma) + df = df.drop(columns=colunas_fantasma) + + if df.empty or len(df.columns) == 0: + logging.warning("DataFrame vazio para %s. Pulando inserção.", table_name) + return None + + col_pk = df.columns[0] + df = df.drop_duplicates(subset=[col_pk]) + df["dt_ingest"] = datetime.now().isoformat() + df["nome_fonte"] = arquivo + + db.insert_data( + data=df.to_dict(orient="records"), + table_name=table_name, + schema=schema, + primary_key=[col_pk], + conflict_fields=[col_pk], + ) + logging.info("Tabela criada/atualizada: %s.%s", schema, table_name) + return table_name + + +# DAG +@dag( + schedule_interval=get_dynamic_schedule("mulheres_censo_dag"), + start_date=datetime(2026, 1, 1), + catchup=False, + default_args={ + "owner": "Rafael, Letícia", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["mulheres", "censo_demografico", "ibge"], +) +def mulheres_censo_demografico_dag() -> None: + """DAG para extrair, despivotar e armazenar dados do Censo 2022.""" + + # Task 1: Listar arquivos no FTP + @task + def listar_arquivos_ftp() -> list: + logging.info("[Task 1] Conectando ao FTP para listar arquivos...") + tema_ibge = _obter_tema_ibge() + arquivos = ClienteIBGE(database=tema_ibge).listar_arquivos_alvo() + + if not arquivos: + logging.warning("Nenhum arquivo encontrado no FTP.") + return arquivos + + # Task 2: Extrair dados do Excel + @task + def extrair_dados_excel(arquivo: str) -> list: + logging.info("[Task 2] Extraindo dados do arquivo: %s", arquivo) + tema_ibge = _obter_tema_ibge() + + buffer = ClienteIBGE(database=tema_ibge).obter_conteudo_arquivo(arquivo) + if not buffer: + raise ValueError(f"Falha ao baixar o arquivo {arquivo}") + + excel_file = pd.ExcelFile(buffer) + abas_validas = [ + a + for a in excel_file.sheet_names + if "gráfico" not in a.lower() and "grafico" not in a.lower() + ] + sheet_name = abas_validas[-1] if abas_validas else excel_file.sheet_names[0] + logging.info("Processando a aba: %s", sheet_name) + + df_aba = excel_file.parse(sheet_name, header=None) + chunks = _identificar_chunks_horizontais(df_aba) + + return [ + resultado + for idx, df_raw in enumerate(chunks) + if ( + resultado := _processar_chunk_excel( + df_raw, idx, len(chunks), sheet_name, arquivo, tema_ibge + ) + ) + ] + + # Task 3: Limpar e inserir dados no banco + @task + def limpar_e_inserir_dados(chunks_data: list) -> str: + logging.info("[Task 3] Limpando nomes de colunas e inserindo dados...") + db = ClientPostgresDB(get_postgres_conn()) + schema = "censo_demografico" + + tabelas = [ + nome + for chunk in chunks_data + if (nome := _processar_chunk_insercao(chunk, db, schema)) + ] + return f"Processadas {len(tabelas)} tabelas com sucesso" + + lista_de_arquivos = listar_arquivos_ftp() + dados_extraidos = extrair_dados_excel.expand(arquivo=lista_de_arquivos) + limpar_e_inserir_dados.expand(chunks_data=dados_extraidos) + + +mulheres_censo_demografico_dag() diff --git a/airflow_lappis/dags/dbt/ipea/models/sources.yml b/airflow_lappis/dags/dbt/ipea/models/sources.yml index 03d4948a..221c0cc7 100644 --- a/airflow_lappis/dags/dbt/ipea/models/sources.yml +++ b/airflow_lappis/dags/dbt/ipea/models/sources.yml @@ -125,3 +125,34 @@ sources: schema: sgac tables: - name: projetos_sgac + + - name: censo_demografico + schema: censo_demografico + tables: + - name: mulheres_tabela_1_tabela_base_do_sidra_10056 + - name: mulheres_tabela_2_tabela_base_do_sidra_10061_parte_1 + - name: mulheres_tabela_2_tabela_base_do_sidra_10061_parte_2 + - name: mulheres_tabela_3_tabela_base_do_sidra_10063_parte_1 + - name: mulheres_tabela_3_tabela_base_do_sidra_10063_parte_2 + - name: mulheres_tabela_3_tabela_base_do_sidra_10063_parte_3 + - name: mulheres_tabela_4_tabela_base_do_sidra_10141_parte_1 + - name: mulheres_tabela_4_tabela_base_do_sidra_10141_parte_2 + - name: mulheres_tabela_5_tabela_base_do_sidra_10253_parte_1 + - name: mulheres_tabela_5_tabela_base_do_sidra_10253_parte_2 + - name: mulheres_tabela_6_tabela_base_do_sidra_10283_parte_1 + - name: mulheres_tabela_6_tabela_base_do_sidra_10283_parte_2 + - name: mulheres_tabela_7_tabela_base_do_sidra_10282_parte_1 + - name: mulheres_tabela_7_tabela_base_do_sidra_10282_parte_2 + - name: mulheres_tabela_8_tabela_base_do_sidra_10329_parte_1 + - name: mulheres_tabela_8_tabela_base_do_sidra_10329_parte_2 + - name: mulheres_tabela_9_tabela_base_do_sidra_10331_parte_1 + - name: mulheres_tabela_9_tabela_base_do_sidra_10331_parte_2 + - name: mulheres_tabela_10_tabela_sidra_10077_e_10078 + - name: mulheres_tabela_11_tabela_base_do_sidra_9882_parte_1 + - name: mulheres_tabela_11_tabela_base_do_sidra_9882_parte_2 + - name: mulheres_tabela_12_br_gr_uf_mu + - name: mulheres_tabela_13_br_gr_uf_mu + - name: mulheres_tabela_14_br_gr_uf_mu + - name: mulheres_tabela_15_br_gr_uf_mu + - name: mulheres_tabela_16_br_gr_uf_mu + - name: mulheres_tabela_17_br_gr_uf_mu diff --git a/airflow_lappis/plugins/cliente_ibge.py b/airflow_lappis/plugins/cliente_ibge.py new file mode 100644 index 00000000..63a6f288 --- /dev/null +++ b/airflow_lappis/plugins/cliente_ibge.py @@ -0,0 +1,74 @@ +import io +import logging +from contextlib import contextmanager + +# ftp.ibge.gov.br é um servidor público do governo +# brasileiro que não oferece suporte a FTPS/SFTP. Apenas dados +# públicos e anônimos são trafegados nessa conexão. +from ftplib import FTP # NOSONAR + +from cliente_base import ClienteBase + + +class ClienteIBGE(ClienteBase): + FTP_HOST = "ftp.ibge.gov.br" + BASE_DIR = "/Censos/Censo_Demografico_2022/" + + def __init__(self, database: str) -> None: + self.host = ClienteIBGE.FTP_HOST + self.database = database + logging.info("[cliente_ibge] Inicializando conexão FTP com: %s", self.host) + + @contextmanager + def _conectar(self): + """ + Abre uma conexão FTP com o servidor público do IBGE. + + Uso: + with self._conectar() as ftp: + ftp.nlst() + """ + full_path = f"{self.BASE_DIR.rstrip('/')}/{self.database.lstrip('/')}" + ftp = FTP(timeout=30) # NOSONAR + try: + ftp.connect(self.host) + resp = ftp.login(user="anonymous", passwd="anonymous@") + logging.info("[cliente_ibge] FTP login: %s", resp) + ftp.set_pasv(True) + ftp.cwd(full_path) + yield ftp + finally: + try: + ftp.quit() + except Exception: + ftp.close() + + # Interface pública + def listar_arquivos_alvo(self) -> list[str]: + """Lista arquivos Excel/CSV do diretório do Censo 2022.""" + try: + with self._conectar() as ftp: + arquivos = ftp.nlst() + + filtrados = [f for f in arquivos if f.endswith((".xlsx", ".xls", ".csv"))] + logging.info("[cliente_ibge] %d arquivo(s) encontrado(s).", len(filtrados)) + return filtrados + + except Exception as exc: + logging.error("[cliente_ibge] Erro ao listar arquivos: %s", exc) + return [] + + def obter_conteudo_arquivo(self, nome_arquivo: str) -> io.BytesIO | None: + """Baixa um arquivo do FTP diretamente para memória.""" + buffer = io.BytesIO() + try: + with self._conectar() as ftp: + logging.info("[cliente_ibge] Baixando: %s", nome_arquivo) + ftp.retrbinary(f"RETR {nome_arquivo}", buffer.write) + + buffer.seek(0) + return buffer + + except Exception as exc: + logging.error("[cliente_ibge] Erro ao baixar '%s': %s", nome_arquivo, exc) + return None diff --git a/pyproject.toml b/pyproject.toml index 06cf5b8d..c67e52fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ sqlalchemy = "*" zeep = "*" imap-tools = "*" astronomer-cosmos = "*" +openpyxl = "^3.1.5" [tool.poetry.group.dev.dependencies] black = "*" diff --git a/requirements.txt b/requirements.txt index 8e86e7aa..5f417239 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ astronomer-cosmos==1.9.0 dbt-postgres==1.7.13 imap-tools==1.10.0 zeep==4.3.1 -apache-airflow-providers-microsoft-mssql==3.7.2 \ No newline at end of file +apache-airflow-providers-microsoft-mssql==3.7.2 +openpyxl \ No newline at end of file From 06948bb2faac4dcc4e95969ae2a0c9c1b9a8a823 Mon Sep 17 00:00:00 2001 From: davi-aguiar-vieira Date: Tue, 12 May 2026 00:21:34 -0300 Subject: [PATCH 305/317] feat: silver chamada publica --- .../silver/chamadas_publicas.sql | 382 ++++++++++++++++++ .../sistema_sisbolsas/silver/schema.yml | 124 ++++++ 2 files changed, 506 insertions(+) create mode 100644 airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/silver/chamadas_publicas.sql diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/silver/chamadas_publicas.sql b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/silver/chamadas_publicas.sql new file mode 100644 index 00000000..e92961e1 --- /dev/null +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/silver/chamadas_publicas.sql @@ -0,0 +1,382 @@ +with + chamadas_base as ( + select + co_chamada_publica, + co_projeto, + co_situacao_chamada, + co_usuario_criacao, + co_programa, + nu_chamada_publica, + nu_ano, + ds_chamada_publica, + ds_numero_sei, + vl_global_estimado, + dt_ini_pesquisa, + dt_fim_pesquisa, + dt_publicacao_dou, + dt_previsao_resultado, + dt_ini_bolsa, + dt_criacao, + dt_inicio_inscricao, + dt_fim_inscricao, + dt_inicio_julgamento, + dt_fim_julgamento, + dt_publicacao_resultado, + tp_moeda, + dt_fim_recurso, + dt_inicio_recurso, + dt_inicio_previsao_bolsa + from {{ ref("sisbolsas_tb_chamada_publica") }} + ), + + chamadas as ( + select + c.co_chamada_publica, + regexp_replace(btrim(c.co_projeto, E' \t\n\r'), '[.]0+$', '') as co_projeto, + c.co_situacao_chamada, + c.co_usuario_criacao, + c.co_programa, + case + when btrim(c.nu_chamada_publica, E' \t\n\r') ~ '^[0-9]+([.]0+)?$' + then regexp_replace(btrim(c.nu_chamada_publica, E' \t\n\r'), '[.]0+$', '') + else btrim(c.nu_chamada_publica, E' \t\n\r') + end as nu_chamada_publica, + case + when btrim(c.nu_ano, E' \t\n\r') ~ '^[0-9]{4}([.]0+)?$' + then regexp_replace(btrim(c.nu_ano, E' \t\n\r'), '[.]0+$', '')::integer + end as ano_chamada, + c.ds_chamada_publica, + c.ds_numero_sei, + c.vl_global_estimado, + case + when c.dt_ini_pesquisa ~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}' + then substring(c.dt_ini_pesquisa from 1 for 10)::date + end as dt_ini_pesquisa, + case + when c.dt_fim_pesquisa ~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}' + then substring(c.dt_fim_pesquisa from 1 for 10)::date + end as dt_fim_pesquisa, + case + when c.dt_publicacao_dou ~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}' + then substring(c.dt_publicacao_dou from 1 for 10)::date + end as dt_publicacao_dou, + case + when c.dt_previsao_resultado ~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}' + then substring(c.dt_previsao_resultado from 1 for 10)::date + end as dt_previsao_resultado, + case + when c.dt_ini_bolsa ~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}' + then substring(c.dt_ini_bolsa from 1 for 10)::date + end as dt_ini_bolsa, + case + when c.dt_criacao ~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}' + then substring(c.dt_criacao from 1 for 10)::date + end as dt_criacao, + case + when c.dt_inicio_inscricao ~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}' + then substring(c.dt_inicio_inscricao from 1 for 10)::date + end as dt_inicio_inscricao, + case + when c.dt_fim_inscricao ~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}' + then substring(c.dt_fim_inscricao from 1 for 10)::date + end as dt_fim_inscricao, + case + when c.dt_inicio_julgamento ~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}' + then substring(c.dt_inicio_julgamento from 1 for 10)::date + end as dt_inicio_julgamento, + case + when c.dt_fim_julgamento ~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}' + then substring(c.dt_fim_julgamento from 1 for 10)::date + end as dt_fim_julgamento, + case + when c.dt_publicacao_resultado ~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}' + then substring(c.dt_publicacao_resultado from 1 for 10)::date + end as dt_publicacao_resultado, + case + when c.dt_fim_recurso ~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}' + then substring(c.dt_fim_recurso from 1 for 10)::date + end as dt_fim_recurso, + case + when c.dt_inicio_recurso ~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}' + then substring(c.dt_inicio_recurso from 1 for 10)::date + end as dt_inicio_recurso, + case + when c.dt_inicio_previsao_bolsa ~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}' + then substring(c.dt_inicio_previsao_bolsa from 1 for 10)::date + end as dt_inicio_previsao_bolsa, + c.tp_moeda, + case + when c.tp_moeda = '1' + then 'Real (R$)' + when c.tp_moeda is null + then null + else 'Estrangeira' + end as moeda + from chamadas_base as c + ), + + usuarios as ( + select + u.co_usuario, + u.ds_nome, + regexp_replace(u.ds_login, '[^0-9]', '', 'g') as ds_login_cpf + from {{ ref("sisbolsas_tb_usuario") }} as u + ), + + programas as ( + select p.co_programa, p.ds_programa + from {{ ref("sisbolsas_tb_programa") }} as p + ), + + situacoes as ( + select s.co_situacao_chamada, s.ds_situacao_chamada + from {{ ref("sisbolsas_tb_situacao_chamada") }} as s + ), + + unidades as ( + select + cu.co_chamada_publica, + string_agg(distinct u.ds_sigla, ' | ' order by u.ds_sigla) as unidades_sigla, + string_agg(distinct u.ds_unidade, ' | ' order by u.ds_unidade) as unidades_nome, + string_agg(distinct e.ds_uf, ' | ' order by e.ds_uf) as ufs + from {{ ref("sisbolsas_tb_chapubli_unidade") }} as cu + left join {{ ref("sisbolsas_tb_unidade") }} as u on cu.co_unidade = u.co_unidade + left join {{ ref("sisbolsas_tb_estado") }} as e on u.co_estado = e.co_estado + group by 1 + ), + + modalidades as ( + select + s.co_chamada_publica, + count(distinct s.co_selecao) as total_selecoes, + string_agg(distinct m.ds_modalidade, ' | ' order by m.ds_modalidade) + as modalidades, + sum( + case + when s.qt_bolsa ~ '^[0-9]+(\\.[0-9]+)?$' + then s.qt_bolsa::numeric + end + ) as cotas, + sum(s.vl_total) as valor_total_selecoes, + max( + case + when s.qt_duracao ~ '^[0-9]+(\\.[0-9]+)?$' + then s.qt_duracao::numeric + end + ) as prazo_meses + from {{ ref("sisbolsas_tb_selecao") }} as s + left join {{ ref("sisbolsas_tb_modalidade") }} as m on s.co_modalidade = m.co_modalidade + group by 1 + ), + + processos as ( + select + p.co_selecao, + p.in_classificacao, + p.in_nao_apto, + p.in_bolsa_ativa + from {{ ref("sisbolsas_tb_processo_seletivo") }} as p + ), + + processos_agg as ( + select + s.co_chamada_publica, + count(*) as total_processos, + sum(case when p.in_classificacao is true then 1 else 0 end) + as total_classificados, + sum(case when p.in_nao_apto is true then 1 else 0 end) as total_nao_aptos, + sum(case when p.in_bolsa_ativa is true then 1 else 0 end) + as total_bolsas_ativas, + sum( + case + when p.in_classificacao is true + and not coalesce(p.in_bolsa_ativa, false) + then 1 + else 0 + end + ) as reservas + from processos as p + left join {{ ref("sisbolsas_tb_selecao") }} as s on p.co_selecao = s.co_selecao + group by 1 + ), + + fontes_chamada as ( + select + cf.co_chamada_publica, + string_agg( + distinct ff.ds_fonte_financeira, + ' | ' + order by ff.ds_fonte_financeira + ) as fontes_recurso_chamada + from {{ ref("sisbolsas_tb_chapubli_fontfina") }} as cf + left join + {{ ref("sisbolsas_tb_fonte_financeira") }} as ff + on cf.co_fonte_financeira = ff.co_fonte_financeira + group by 1 + ), + + coordenadores_chamada as ( + select + c.co_chamada_publica, + string_agg( + distinct coalesce(u.ds_nome, c.ds_cpf), + ' | ' + order by coalesce(u.ds_nome, c.ds_cpf) + ) as coordenador_chamada + from {{ ref("sisbolsas_tb_coordenador") }} as c + left join usuarios as u on c.ds_cpf = u.ds_login_cpf + group by 1 + ), + + projetos as ( + select + p.projetoid, + p.projetoid::text as co_projeto, + p.tituloprojeto, + p.numeroprojeto, + p.anoprojeto, + p.coordenadorid, + p.diretoriaid, + p.statusprojetoid + from {{ ref("ipea_pro_projetos") }} as p + ), + + diretorias as ( + select d.diretoriaid, d.diretorianome, d.diretoriasigla + from {{ ref("ipea_pro_diretorias") }} as d + ), + + coordenadores_projeto as ( + select sp.servidorpublicoid, sp.nomeservidor + from {{ ref("ipea_pro_servidorespublicos") }} as sp + ), + + status_projetos as ( + select s.statusprojetoid, s.nomestatus + from {{ ref("ipea_pro_statusprojetos") }} as s + ), + + fontes_projeto as ( + select + fr.projetoid::text as co_projeto, + string_agg( + distinct coalesce(ifr.nomeitemfontereceita, fr.descricaofonte), + ' | ' + order by coalesce(ifr.nomeitemfontereceita, fr.descricaofonte) + ) as fontes_recurso_projeto, + sum(fr.valortotalfonte) as valor_total_fontes_projeto + from {{ ref("ipea_pro_fontesreceitas") }} as fr + left join + {{ ref("ipea_pro_itemfontereceitas") }} as ifr + on fr.itemfontereceitaid = ifr.itemfontereceitaid + group by 1 + ), + + fontes_projeto_por_ano_raw as ( + select + fr.projetoid, + coalesce( + extract(year from fr.datainiciofonte), + extract(year from fr.datafinalfonte) + )::integer as ano, + sum(fr.valortotalfonte) as valor_ano + from {{ ref("ipea_pro_fontesreceitas") }} as fr + group by 1, 2 + ), + + fontes_projeto_por_ano as ( + select + f.projetoid::text as co_projeto, + jsonb_object_agg(f.ano, f.valor_ano order by f.ano) + filter (where f.ano is not null) as valor_por_ano + from fontes_projeto_por_ano_raw as f + group by 1 + ) + +select + c.co_chamada_publica as chamada_id, + c.ds_numero_sei as processo_sei, + concat_ws('/', c.nu_chamada_publica, c.ano_chamada::text) as numero_chamada, + c.ano_chamada, + concat_ws('/', c.nu_chamada_publica, c.ano_chamada::text) as numero_chamada_ano, + c.ds_chamada_publica as descricao_chamada, + prog.ds_programa as tipo_chamada, + c.co_programa as programa_id, + sit.ds_situacao_chamada as situacao_chamada, + c.co_situacao_chamada as situacao_chamada_id, + c.co_projeto as projeto_id, + proj.tituloprojeto as projeto_titulo, + proj.numeroprojeto as projeto_numero, + proj.anoprojeto as projeto_ano, + dir.diretorianome as diretoria, + dir.diretoriasigla as diretoria_sigla, + coalesce(coord_proj.nomeservidor, coord_chamada.coordenador_chamada) + as coordenador, + coord_chamada.coordenador_chamada, + coord_proj.nomeservidor as coordenador_projeto, + status_projeto.nomestatus as status_projeto, + unidades.unidades_sigla, + unidades.unidades_nome, + unidades.ufs, + modalidades.modalidades, + modalidades.total_selecoes, + modalidades.prazo_meses, + c.dt_ini_pesquisa as inicio_pesquisa, + c.dt_fim_pesquisa as fim_pesquisa, + case + when c.dt_ini_pesquisa is not null and c.dt_fim_pesquisa is not null + then (c.dt_fim_pesquisa - c.dt_ini_pesquisa) + end as prazo_pesquisa_dias, + c.dt_inicio_inscricao as inicio_inscricao, + c.dt_fim_inscricao as fim_inscricao, + c.dt_inicio_julgamento as inicio_julgamento, + c.dt_fim_julgamento as fim_julgamento, + c.dt_publicacao_dou as publicacao_dou, + c.dt_previsao_resultado as previsao_resultado, + c.dt_publicacao_resultado as publicacao_resultado, + c.dt_inicio_previsao_bolsa as inicio_previsao_bolsa, + c.dt_ini_bolsa as inicio_bolsa, + c.dt_inicio_recurso as inicio_recurso, + c.dt_fim_recurso as fim_recurso, + c.dt_criacao as data_criacao, + c.moeda, + coalesce( + c.vl_global_estimado, + modalidades.valor_total_selecoes, + fontes_projeto.valor_total_fontes_projeto + ) as valor_total_chamada, + c.vl_global_estimado as valor_global_estimado, + modalidades.valor_total_selecoes, + fontes_projeto.valor_total_fontes_projeto, + fontes_projeto_por_ano.valor_por_ano, + coalesce( + fontes_chamada.fontes_recurso_chamada, + fontes_projeto.fontes_recurso_projeto + ) as fonte_recurso, + fontes_chamada.fontes_recurso_chamada, + fontes_projeto.fontes_recurso_projeto, + modalidades.cotas, + processos.reservas, + processos.total_processos, + processos.total_classificados, + processos.total_nao_aptos, + processos.total_bolsas_ativas, + usuario_criacao.ds_nome as usuario_criacao +from chamadas as c +left join programas as prog on c.co_programa = prog.co_programa +left join situacoes as sit on c.co_situacao_chamada = sit.co_situacao_chamada +left join unidades on c.co_chamada_publica = unidades.co_chamada_publica +left join modalidades on c.co_chamada_publica = modalidades.co_chamada_publica +left join processos_agg as processos on c.co_chamada_publica = processos.co_chamada_publica +left join fontes_chamada on c.co_chamada_publica = fontes_chamada.co_chamada_publica +left join coordenadores_chamada as coord_chamada + on c.co_chamada_publica = coord_chamada.co_chamada_publica +left join projetos as proj on c.co_projeto = proj.co_projeto +left join diretorias as dir on proj.diretoriaid = dir.diretoriaid +left join coordenadores_projeto as coord_proj + on proj.coordenadorid = coord_proj.servidorpublicoid +left join status_projetos as status_projeto + on proj.statusprojetoid = status_projeto.statusprojetoid +left join fontes_projeto on c.co_projeto = fontes_projeto.co_projeto +left join fontes_projeto_por_ano on c.co_projeto = fontes_projeto_por_ano.co_projeto +left join usuarios as usuario_criacao on c.co_usuario_criacao = usuario_criacao.co_usuario diff --git a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/silver/schema.yml b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/silver/schema.yml index 70cbaa5b..0aabe4b6 100644 --- a/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/silver/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/sistema_sisbolsas/silver/schema.yml @@ -73,3 +73,127 @@ models: description: Quantidade de mulheres faltantes para atingir a meta mínima de 40%. - name: negros_faltantes_meta description: Quantidade de negros faltantes para atingir a meta mínima de 30%. + + - name: chamadas_publicas + description: > + Modelo silver com uma linha por chamada pública do Sisbolsas, integrando + dados da chamada, seleções, unidades, fontes de recurso e informações de + projeto do ipea_pro para apoiar relatórios e filtros por situação. + meta: + tags: + - silver + columns: + - name: chamada_id + description: Identificador da chamada pública (co_chamada_publica). + - name: processo_sei + description: Número do processo SEI vinculado à chamada. + - name: numero_chamada + description: Número/ano concatenado da chamada pública (nu_chamada_publica/nu_ano). + - name: ano_chamada + description: Ano da chamada pública, normalizado a partir de nu_ano. + - name: numero_chamada_ano + description: Número/ano concatenado da chamada pública. + - name: descricao_chamada + description: Descrição ou título da chamada pública. + - name: tipo_chamada + description: Tipo da chamada pública baseado no programa (ds_programa). + - name: programa_id + description: Identificador do programa da chamada pública. + - name: situacao_chamada + description: Situação textual da chamada pública. + - name: situacao_chamada_id + description: Código da situação da chamada pública. + - name: projeto_id + description: Identificador do projeto associado à chamada pública. + - name: projeto_titulo + description: Título do projeto no ipea_pro. + - name: projeto_numero + description: Número do projeto no ipea_pro. + - name: projeto_ano + description: Ano do projeto no ipea_pro. + - name: diretoria + description: Nome da diretoria do projeto. + - name: diretoria_sigla + description: Sigla da diretoria do projeto. + - name: coordenador + description: Coordenador consolidado (projeto ou chamada). + - name: coordenador_chamada + description: Coordenadores cadastrados na chamada pública. + - name: coordenador_projeto + description: Coordenador do projeto no ipea_pro. + - name: status_projeto + description: Status do projeto no ipea_pro. + - name: unidades_sigla + description: Siglas das unidades vinculadas à chamada pública. + - name: unidades_nome + description: Nomes das unidades vinculadas à chamada pública. + - name: ufs + description: UFs das unidades vinculadas à chamada pública. + - name: modalidades + description: Modalidades agregadas das seleções da chamada. + - name: total_selecoes + description: Quantidade de seleções vinculadas à chamada pública. + - name: prazo_meses + description: Maior duração (em meses) registrada nas seleções da chamada. + - name: inicio_pesquisa + description: Data de início da pesquisa informada na chamada pública. + - name: fim_pesquisa + description: Data de término da pesquisa informada na chamada pública. + - name: prazo_pesquisa_dias + description: Diferença em dias entre início e término da pesquisa. + - name: inicio_inscricao + description: Data de início da inscrição da chamada pública. + - name: fim_inscricao + description: Data de término da inscrição da chamada pública. + - name: inicio_julgamento + description: Data de início do julgamento da chamada pública. + - name: fim_julgamento + description: Data de término do julgamento da chamada pública. + - name: publicacao_dou + description: Data de publicação no DOU da chamada pública. + - name: previsao_resultado + description: Data de previsão do resultado da chamada pública. + - name: publicacao_resultado + description: Data de publicação do resultado da chamada pública. + - name: inicio_previsao_bolsa + description: Data de previsão de início das bolsas. + - name: inicio_bolsa + description: Data de início das bolsas da chamada pública. + - name: inicio_recurso + description: Data de início do período de recurso da chamada pública. + - name: fim_recurso + description: Data de término do período de recurso da chamada pública. + - name: data_criacao + description: Data de criação do registro da chamada pública. + - name: moeda + description: Moeda indicada para a chamada pública. + - name: valor_total_chamada + description: Valor total consolidado da chamada pública. + - name: valor_global_estimado + description: Valor global estimado registrado na chamada pública. + - name: valor_total_selecoes + description: Soma do valor total das seleções vinculadas à chamada. + - name: valor_total_fontes_projeto + description: Soma do valor total das fontes do projeto no ipea_pro. + - name: valor_por_ano + description: Mapa JSON com soma por ano das fontes do projeto. + - name: fonte_recurso + description: Fonte de recurso consolidada (Sisbolsas ou ipea_pro). + - name: fontes_recurso_chamada + description: Fontes financeiras associadas à chamada pública. + - name: fontes_recurso_projeto + description: Fontes financeiras associadas ao projeto no ipea_pro. + - name: cotas + description: Total de bolsas/vagas a partir de qt_bolsa das seleções. + - name: reservas + description: Classificados sem bolsa ativa (proxy para reservas). + - name: total_processos + description: Total de processos seletivos vinculados à chamada. + - name: total_classificados + description: Total de processos com in_classificacao verdadeiro. + - name: total_nao_aptos + description: Total de processos com in_nao_apto verdadeiro. + - name: total_bolsas_ativas + description: Total de processos com bolsa ativa. + - name: usuario_criacao + description: Usuário que criou a chamada pública. From bb093ea008dab9dc3e7b663cc2c3e77349f40f68 Mon Sep 17 00:00:00 2001 From: Maria Clara Sena Date: Thu, 14 May 2026 14:53:32 -0300 Subject: [PATCH 306/317] feat: adiciona colunas fornecedor_tipo e fornecedor_nome nos modelos de contrato (#275) --- .../gold/contratos_comparativo_mensal.sql | 5 ++- .../gold/contratos_somatorio.sql | 6 +++- .../ipea/models/contratos_dbt/gold/schema.yml | 21 +++++++++++ .../silver/contratos_empenhos.sql | 6 ++++ .../silver/contratos_estagios.sql | 34 +++++++++++------- .../silver/contratos_faturas.sql | 4 +++ .../silver/cronogramas_faturas_mensal.sql | 21 +++++++++-- .../models/contratos_dbt/silver/schema.yml | 36 +++++++++++++++++++ 8 files changed, 117 insertions(+), 16 deletions(-) diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql index a1565639..9b529c44 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_comparativo_mensal.sql @@ -29,7 +29,7 @@ with preenchimento as (select contrato_id, mes_ref from {{ ref("preenchimento_meses") }}), contratos as ( - select id, numero, dt_ingest as dt_ingest_contratos + select id, numero, fornecedor_cnpj_cpf_idgener, fornecedor_tipo, fornecedor_nome, dt_ingest as dt_ingest_contratos from {{ ref("contratos") }} ), @@ -63,6 +63,9 @@ select ccm.siafi_restos_a_pagar, ccm.siafi_restos_a_pagar_pago, c.numero, + c.fornecedor_cnpj_cpf_idgener, + c.fornecedor_tipo, + c.fornecedor_nome, greatest(ccm.dt_ingest::timestamptz, c.dt_ingest_contratos::timestamptz) as dt_ingest from comparativo_mensal as ccm left join contratos as c on ccm.contrato_id = c.id diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_somatorio.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_somatorio.sql index 523dd547..c75887b5 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_somatorio.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/contratos_somatorio.sql @@ -1,5 +1,9 @@ select contrato_id, + numero, + fornecedor_cnpj_cpf_idgener, + fornecedor_tipo, + fornecedor_nome, sum(comprasgov_valor_cronograma) as total_cronograma, sum(comprasgov_valor_faturas) as total_faturas, sum(comprasgov_saldo_contratual_disponivel) as total_saldo_disponivel, @@ -15,4 +19,4 @@ select max(dt_ingest) as dt_ingest from {{ ref("contratos_comparativo_mensal") }} -group by contrato_id +group by contrato_id, numero, fornecedor_cnpj_cpf_idgener, fornecedor_tipo, fornecedor_nome diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/schema.yml b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/schema.yml index 9f9b9587..831fe06d 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/gold/schema.yml @@ -88,6 +88,15 @@ models: - name: numero description: > Número do contrato no sistema ComprasGov. + - name: fornecedor_cnpj_cpf_idgener + description: > + CNPJ, CPF ou identificador genérico do fornecedor do contrato. + - name: fornecedor_tipo + description: > + Tipo do fornecedor do contrato. + - name: fornecedor_nome + description: > + Nome ou razão social do fornecedor associado ao contrato. - name: mes_ref description: > Mês de referência da informação financeira, preenchido para todos os meses entre o início e fim do contrato. @@ -125,6 +134,18 @@ models: - name: contrato_id description: > Identificador único do contrato. + - name: numero + description: > + Número do contrato no sistema ComprasGov. + - name: fornecedor_cnpj_cpf_idgener + description: > + CNPJ, CPF ou identificador genérico do fornecedor do contrato. + - name: fornecedor_tipo + description: > + Tipo do fornecedor do contrato. + - name: fornecedor_nome + description: > + Nome ou razão social do fornecedor associado ao contrato. - name: total_cronograma description: > Soma de todos os valores programados no cronograma do contrato. diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_empenhos.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_empenhos.sql index 79cacef9..50263f32 100755 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_empenhos.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_empenhos.sql @@ -209,7 +209,10 @@ with contratos as ( select id, + fornecedor_tipo, fornecedor_nome, + fornecedor_cnpj_cpf_idgener, + numero, unidades_requisitantes, objeto, vigencia_inicio, @@ -234,7 +237,10 @@ select ce.despesas_empenhadas, ce.despesas_liquidadas, ce.despesas_pagas, + cc.fornecedor_tipo, cc.fornecedor_nome, + cc.fornecedor_cnpj_cpf_idgener, + cc.numero, cc.unidades_requisitantes, cc.objeto, cc.vigencia_inicio, diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_estagios.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_estagios.sql index a833e1ee..3be3a6b8 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_estagios.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_estagios.sql @@ -98,19 +98,29 @@ with union select * from joined_table_3 + ), + + contratos as ( + select id, numero, fornecedor_cnpj_cpf_idgener, fornecedor_tipo, fornecedor_nome, dt_ingest as dt_ingest_contratos + from {{ ref("contratos") }} ) -- select - contrato_id, - mes_lancamento, - sum(valor_empenhado) as valor_empenhado, - sum(valor_liquidado) as valor_liquidado, - sum(valor_pago) as valor_pago, - sum(restos_a_pagar) as restos_a_pagar, - sum(restos_a_pagar_pago) as restos_a_pagar_pago, - max(dt_ingest) as dt_ingest -from result_table -where contrato_id is not null -group by 1, 2 -order by contrato_id, mes_lancamento + r.contrato_id, + c.numero, + c.fornecedor_cnpj_cpf_idgener, + c.fornecedor_tipo, + c.fornecedor_nome, + r.mes_lancamento, + sum(r.valor_empenhado) as valor_empenhado, + sum(r.valor_liquidado) as valor_liquidado, + sum(r.valor_pago) as valor_pago, + sum(r.restos_a_pagar) as restos_a_pagar, + sum(r.restos_a_pagar_pago) as restos_a_pagar_pago, + greatest(max(r.dt_ingest), max(c.dt_ingest_contratos)) as dt_ingest +from result_table r +left join contratos c on r.contrato_id = c.id +where r.contrato_id is not null +group by 1, 2, 3, 4, 5, 6 +order by r.contrato_id, r.mes_lancamento diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_faturas.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_faturas.sql index 7ca2bcff..4d983e70 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_faturas.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/contratos_faturas.sql @@ -5,6 +5,8 @@ with select id::int as contrato_id, fornecedor_cnpj_cpf_idgener, + fornecedor_tipo, + fornecedor_nome, processo as processo_contrato, numero as numero_contrato, objeto as objeto_contrato, @@ -21,6 +23,8 @@ select c.numero_contrato, c.processo_contrato as contrato_processo, c.fornecedor_cnpj_cpf_idgener, + c.fornecedor_tipo, + c.fornecedor_nome, c.objeto_contrato, c.unidades_requisitantes, f.tipolistafatura_id, diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/cronogramas_faturas_mensal.sql b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/cronogramas_faturas_mensal.sql index 9cc2eb1e..a581d292 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/cronogramas_faturas_mensal.sql +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/cronogramas_faturas_mensal.sql @@ -74,7 +74,24 @@ with greatest(dt_ingest_pago, dt_ingest_pendente) AS dt_ingest from joined_table order by contrato_id, mes_ref + ), + + contratos as ( + select id::text as contrato_id, numero, fornecedor_cnpj_cpf_idgener, fornecedor_tipo, fornecedor_nome, dt_ingest + from {{ ref("contratos") }} ) -select * -from joined_ajustado +select + ja.contrato_id, + ja.mes_ref, + ja.valor_cronograma, + ja.valor_faturas_pagas, + ja.valor_faturas_pendentes, + ja.saldo_contratual_disponivel, + c.numero, + c.fornecedor_cnpj_cpf_idgener, + c.fornecedor_tipo, + c.fornecedor_nome, + greatest(ja.dt_ingest::timestamp with time zone, c.dt_ingest::timestamp with time zone) as dt_ingest +from joined_ajustado ja +left join contratos c using (contrato_id) diff --git a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/schema.yml b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/schema.yml index 9e7adfaa..28f174c0 100644 --- a/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/schema.yml +++ b/airflow_lappis/dags/dbt/ipea/models/contratos_dbt/silver/schema.yml @@ -58,6 +58,12 @@ models: - name: fornecedor_nome description: > Nome do fornecedor associado ao contrato. + - name: fornecedor_cnpj_cpf_idgener + description: > + CNPJ, CPF ou identificador genérico do fornecedor do contrato. + - name: numero + description: > + Número do contrato no sistema ComprasGov. - name: unidades_requisitantes description: > Unidades requisitantes associadas ao contrato. @@ -87,6 +93,18 @@ models: - name: contrato_id description: > Identificador único do contrato relacionado aos estágios. + - name: numero + description: > + Número do contrato no sistema ComprasGov. + - name: fornecedor_cnpj_cpf_idgener + description: > + CNPJ, CPF ou identificador genérico do fornecedor do contrato. + - name: fornecedor_tipo + description: > + Tipo do fornecedor do contrato. + - name: fornecedor_nome + description: > + Nome ou razão social do fornecedor associado ao contrato. - name: mes_lancamento description: > Mês em que o estágio da despesa foi registrado no SIAFI. @@ -164,6 +182,12 @@ models: - name: fornecedor_cnpj_cpf_idgener description: > CNPJ, CPF ou identificador genérico do fornecedor do contrato. + - name: fornecedor_tipo + description: > + Tipo do fornecedor do contrato. + - name: fornecedor_nome + description: > + Nome ou razão social do fornecedor associado ao contrato. - name: objeto_contrato description: > Descrição do objeto contratado. @@ -270,6 +294,18 @@ models: - name: contrato_id description: > Identificador único do contrato. + - name: numero + description: > + Número do contrato no sistema ComprasGov. + - name: fornecedor_cnpj_cpf_idgener + description: > + CNPJ, CPF ou identificador genérico do fornecedor do contrato. + - name: fornecedor_tipo + description: > + Tipo do fornecedor do contrato. + - name: fornecedor_nome + description: > + Nome ou razão social do fornecedor associado ao contrato. - name: mes_ref description: > Mês de referência dos valores programados e faturados. From a04252cba42139863c6817a1d41360139ca16b75 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Mon, 18 May 2026 10:24:04 -0300 Subject: [PATCH 307/317] fix: sources realocado corretamente --- airflow_lappis/dags/dbt/mir/models/sources.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/airflow_lappis/dags/dbt/mir/models/sources.yml b/airflow_lappis/dags/dbt/mir/models/sources.yml index 5ced9979..02918481 100644 --- a/airflow_lappis/dags/dbt/mir/models/sources.yml +++ b/airflow_lappis/dags/dbt/mir/models/sources.yml @@ -28,11 +28,12 @@ sources: tables: - name: senadores - name: legislaturas + - name: senadores_historico + - name: dados_abertos schema: dados_abertos tables: - name: logo_partidos - - name: senadores_historico - name: transfere_gov schema: transfere_gov From 9cbcf719ae7a2536fb87de7c78db511e5c6d7c76 Mon Sep 17 00:00:00 2001 From: Rafael Melo Matuda Date: Mon, 18 May 2026 21:48:31 -0300 Subject: [PATCH 308/317] =?UTF-8?q?docs:=20adionando=20o=20guia=20de=20con?= =?UTF-8?q?tribui=C3=A7=C3=A3o=20no=20reposit=C3=B3rio.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Letícia Hladczuk --- CONTRIBUTING.md | 371 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..8c943d01 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,371 @@ +# Guia de Contribuição — Gov Hub BR + +Antes de começar, obrigado por considerar contribuir com o **Gov Hub BR**! + +O GovHub BR é uma plataforma open-source com o propósito de transformar dados públicos em ativos estratégicos para a administração pública e a sociedade. Toda contribuição — seja código, pipelines de dados, documentação, ideias ou feedback — é bem-vinda. + +--- + +## Índice + +- [Código de Conduta](#código-de-conduta) +- [Estrutura do Projeto](#estrutura-do-projeto) +- [Configurando o Ambiente Local](#configurando-o-ambiente-local) +- [Convenção de Branches](#convenção-de-branches) +- [Padrão de Commits](#padrão-de-commits) +- [Fluxo de Pull Request](#fluxo-de-pull-request) +- [Como Pegar ou Atribuir Issues](#como-pegar-ou-atribuir-issues) +- [Processo de Code Review](#processo-de-code-review) +- [Executando os Testes](#executando-os-testes) +- [Padrões de Código e Lint](#padrões-de-código-e-lint) +- [Boas Práticas Gerais](#boas-práticas-gerais) + +--- +## Código de Conduta +Por favor, leia nosso [Código de Conduta](CODE_OF_CONDUCT.md). Ele está em vigor o tempo todo. Esperamos que seja respeitado por todos que contribuem para este projeto. Comportamentos inadequados não serão tolerados. + +## Estrutura do Projeto + +``` +. +├── airflow/ +│ ├── dags/ +│ └── plugins/ +├── dbt/ +│ └── models/ +├── jupyter/ +│ └── notebooks/ +├── superset/ +│ └── dashboards/ +├── docker-compose.yml +├── Makefile +├── CONTRIBUTING.md +└── README.md +``` + +> Consulte a [documentação de arquitetura](https://gov-hub.io/govhub/documentacao/arquitetura/) para entender o fluxo completo dos dados. + +--- + +## Configurando o Ambiente Local + +### Pré-requisitos + +- [Docker](https://docs.docker.com/get-docker/) e [Docker Compose](https://docs.docker.com/compose/) +- [Git](https://git-scm.com/) >= 2.x +- Python = 3.11 +- Make +- Acesso às credenciais dos sistemas estruturantes (quando necessário) — veja o [guia de credenciais](https://gov-hub.io/govhub/documentacao/tutoriais/sistemas-estruturantes/acesso-apis-siafi-siape/) + +### Passo a passo + +#### 1. Faça o fork pelo GitHub e clone o seu fork +```bash +git clone https://github.com//data-application-gov-hub.git +cd data-application-gov-hub +``` + +#### 2. Adicione o repositório original como remote "upstream" +```bash +git remote add upstream https://github.com/GovHub-br/data-application-gov-hub.git +``` + +#### 3. Execute a configuração usando Make: +```bash +make setup +``` + +Isso irá: +- Criar os ambientes virtuais necessários +- Instalar as dependências +- Configurar os hooks de pre-commit +- Preparar o ambiente de desenvolvimento + +#### 4. Copie as variáveis de ambiente e configure conforme necessário + +```bash +cp local.env .env +``` + +#### 5. Suba o ambiente com Docker Compose +```bash +docker compose up -d +``` + +#### 6. Acesse os serviços locais +```bash +# Apache Airflow: http://localhost:8080 +# Apache Superset: http://localhost:8088 +# Jupyter: http://localhost:8888 +``` + +Para instruções detalhadas de cada componente, consulte a [documentação de instalação](https://gov-hub.io/govhub/documentacao/instalacao/). + +--- + +## Convenção de Branches + +Crie sua branch a partir de `main` com um nome descritivo seguindo o padrão: + +``` +/ +``` + +| Tipo | Quando usar | +|------|-------------| +| `feat` | Nova funcionalidade ou pipeline | +| `fix` | Correção de bug ou inconsistência de dados | +| `docs` | Alterações apenas em documentação | +| `refactor` | Refatoração sem mudança de comportamento | +| `ci` | Mudanças em CI/CD ou infraestrutura | +| `test` | Adição ou ajuste de testes (DBT ou unitários) | +| `chore` | Tarefas de manutenção gerais | + +**Exemplos:** + +```bash +git checkout -b feat/integracao-siafi-despesas +git checkout -b fix/corrigir-modelo-silver-servidores +git checkout -b docs/atualizar-dicionario-siape +git checkout -b ci/ajustar-pipeline-kubernetes +``` + +--- + +## Padrão de Commits + +As mensagens de commit devem seguir o padrão [Conventional Commits](https://www.conventionalcommits.org/pt-br/), conforme adotado pelo projeto. + +### Formato + +``` +(): +``` + +### Tipos aceitos + +| Tipo | Descrição | +|------|-----------| +| `feat` | Nova funcionalidade ou novo modelo/pipeline | +| `fix` | Correção de bug ou erro em transformação | +| `docs` | Documentação (MkDocs, docstrings, README) | +| `ci` | Integração contínua, Docker, Kubernetes, Airflow | +| `refactor` | Melhoria de código sem alteração de comportamento | +| `test` | Testes DBT (`schema.yml`) ou testes unitários | +| `chore` | Manutenção geral, atualização de dependências | +| `perf` | Melhoria de performance em queries ou pipelines | + +### Exemplos + +```bash +# Nova funcionalidade +git commit -m "feat(dbt): adicionar modelo gold de execução orçamentária por UG" + +# Correção de bug +git commit -m "fix(dag): corrigir timeout na DAG de ingestão do SIAPE" + +# Documentação +git commit -m "docs: adicionar dicionário de dados para domínio de pessoal" + +# CI/Infraestrutura +git commit -m "ci: ajustar configuração do Astronomer Cosmos para DBT 1.8" + +# Referenciando issue +git commit -m "feat(dbt): criar snapshot de cargos e funções + +Closes #42" +``` + +Quando necessário, utilize a descrição estendida do commit para detalhar motivações, impactos e decisões técnicas importantes. Isso facilita o entendimento histórico das mudanças e contribui para uma base de código mais sustentável e auditável. + +--- + +## Fluxo de Pull Request + +``` +fork → branch → commits → push → Pull Request → review → merge +``` + +### Passo a passo + +```bash +# 1. Mantenha sua branch atualizada com o upstream antes de enviar +git fetch upstream +git rebase upstream/main + +# 2. Faça push da sua branch para o seu fork +git push origin feat/integracao-siafi-despesas + +# 3. Abra um Pull Request no GitHub apontando para a branch main do repositório principal +``` + +Antes de enviar, certifique-se de que sua alteração está funcionando corretamente, sem quebrar funcionalidades existentes, e que segue os padrões definidos pelo projeto. + +### Checklist antes de abrir o PR + +- [ ] As alterações funcionam corretamente no ambiente local (Docker Compose) +- [ ] Os testes DBT passam (`dbt test`) +- [ ] A branch está atualizada com `upstream/main` +- [ ] O título do PR segue o padrão Conventional Commits +- [ ] O PR referencia a issue relacionada (`Closes #`) +- [ ] Documentação atualizada, se aplicável +- [ ] Prints, logs ou exemplos de output incluídos quando relevante + +### Template de Pull Request + +Utilize o modelo disponível em `.github/PULL_REQUEST_TEMPLATE.md` ao abrir um PR: + +```markdown +## Descrição + + +## Tipo de mudança +- [ ] Nova funcionalidade / pipeline +- [ ] Correção de bug ou inconsistência de dados +- [ ] Refatoração de modelo DBT +- [ ] Documentação +- [ ] Infraestrutura / CI +- [ ] Outro: ___ + +## Issues relacionadas +Closes # + +## Como testar / validar + +1. +2. + +## Evidências + + +## Checklist +- [ ] Testes DBT adicionados/atualizados +- [ ] Documentação atualizada +- [ ] Sem dados sensíveis ou credenciais no código +``` + +--- + +## Como Pegar ou Atribuir Issues + +Toda solicitação de mudança, correção de bug ou sugestão de melhoria deve ser registrada por meio de uma issue, usando os templates disponíveis em `.github/ISSUE_TEMPLATE/`. Certifique-se de preencher todos os campos obrigatórios com informações precisas: contexto, impacto e possíveis caminhos de solução. + +1. Navegue até a aba [**Issues**](https://github.com/GovHub-br/govhub/issues) do repositório. +2. Filtre por labels como `good first issue` ou `help wanted` para começar. +3. Leia a descrição completa e verifique se a issue já tem alguém atribuído. +4. Comente na issue manifestando interesse: **"Posso trabalhar nessa issue?"**. +5. Aguarde a confirmação de um mantenedor antes de iniciar. + +> **Importante:** não abra PRs para issues já atribuídas a outro contribuidor sem combinar antes. + +### Templates disponíveis + +- `bug_report.md` — erros em modelos DBT, DAGs ou infraestrutura +- `feature_request.md` — novas integrações ou funcionalidades +- `documentation.md` — melhorias na documentação ou dicionário de dados + +--- + +## Processo de Code Review + +### Para quem submete o PR + +- Responda a todos os comentários de revisão de forma objetiva. +- Implemente as mudanças solicitadas em novos commits (evite `push --force` após o início do review). +- Aguarde nova aprovação antes de solicitar merge. + +### Para quem faz o review + +- Seja construtivo e específico — explique o *porquê* da sugestão. +- Diferencie bloqueadores (`X`) de sugestões opcionais. +- Verifique especialmente: qualidade dos modelos DBT, linhagem de dados, testes de qualidade e impacto em pipelines existentes. +- O merge é responsabilidade dos mantenedores do projeto. + +--- + +## Executando os Testes + +### Testes DBT + +```bash +# Executar todos os testes de qualidade de dados +dbt test + +# Executar testes de um modelo específico +dbt test --select modelo_gold_orcamento + +# Compilar os modelos sem executar (validação de SQL) +dbt compile + +# Gerar e visualizar a documentação DBT +dbt docs generate +dbt docs serve +``` + +Consulte a documentação de [testes DBT](https://gov-hub.io/govhub/documentacao/tutoriais/dbt/testes/) para mais detalhes. +### +### Validação de DAGs (Airflow) + +```bash +# Verificar sintaxe de todas as DAGs +python -m pytest tests/ -v + +# Testar execução de uma DAG específica +airflow dags test +``` + +> Toda contribuição que adiciona ou altera modelos DBT deve incluir testes de qualidade no `schema.yml` correspondente (ex.: `not_null`, `unique`, `accepted_values`). + +--- + +## Padrões de Código e Lint + +### DBT / SQL + +- Siga a [Arquitetura Medallion](https://gov-hub.io/govhub/documentacao/tutoriais/dbt/arquitetura-medallion/) (bronze → silver → gold). +- Nomeie modelos com prefixo da camada: `bronze_`, `silver_`, `gold_`. +- Documente cada modelo e coluna no arquivo `schema.yml` correspondente. +- Use [macros DBT](https://gov-hub.io/govhub/documentacao/tutoriais/dbt/macros/) para lógica reutilizável. + +### Python (DAGs e scripts) + +```bash +# Instalar dependências de desenvolvimento +pip install -r requirements-dev.txt + +# Verificar lint +flake8 dags/ --max-line-length=120 + +# Formatar código +black dags/ +``` + +### Documentação (MkDocs) + +```bash +# Visualizar documentação localmente +mkdocs serve + +# Construir site estático +mkdocs build +``` + +--- + +## Boas Práticas Gerais + +- **PRs pequenos e focados:** um PR por funcionalidade ou correção facilita o review. +- **Sem credenciais no código:** nunca commite tokens, senhas ou chaves de API — use variáveis de ambiente. Veja o [guia de credenciais](https://gov-hub.io/govhub/documentacao/tutoriais/sistemas-estruturantes/acesso-apis-siafi-siape/). +- **Sem dados sensíveis:** não inclua dados reais de servidores públicos ou cidadãos em testes ou exemplos. +- **Documente junto ao código:** atualize `schema.yml` e a documentação no mesmo PR da mudança. +- **Compatibilidade:** valide que sua contribuição não quebra pipelines ou modelos existentes. +- **Novas dependências:** discuta em uma issue antes de adicionar novos pacotes ou serviços. +- **Idioma:** código e comentários técnicos em **inglês**; issues, PRs e documentação em **português**. + +--- + +
+ Feito com 💜 pela comunidade GovHub BR  ·  + Reportar problema  ·  + Documentação +
\ No newline at end of file From 57ce77ed71ddfbaee6f8b9afe07a2fed79103650 Mon Sep 17 00:00:00 2001 From: Rafael Melo Matuda Date: Mon, 18 May 2026 21:58:12 -0300 Subject: [PATCH 309/317] =?UTF-8?q?docs:=20adionando=20a=20se=C3=A7=C3=A3o?= =?UTF-8?q?=20FAQ.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Letícia Hladczuk --- CONTRIBUTING.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8c943d01..448baeaf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -362,6 +362,20 @@ mkdocs build - **Novas dependências:** discuta em uma issue antes de adicionar novos pacotes ou serviços. - **Idioma:** código e comentários técnicos em **inglês**; issues, PRs e documentação em **português**. +## FAQ + +**Posso contribuir sem ter sido atribuído a uma issue?** +> Sim, para correções triviais como typos na documentação. Para mudanças em modelos DBT, DAGs ou infraestrutura, abra ou comente uma issue antes. + +**Não tenho acesso aos sistemas estruturantes (SIAFI, SIAPE). Posso contribuir?** +> Sim! Muitas contribuições não exigem acesso a APIs governamentais — documentação, testes DBT com dados sintéticos, melhorias de infraestrutura e dashboards no Superset são exemplos. + +**Onde posso tirar dúvidas técnicas sobre DBT, Airflow ou Superset?** +> Consulte os [tutoriais da documentação](https://gov-hub.io/govhub/documentacao/instalacao/) ou abra uma [Discussion](https://github.com/GovHub-br/govhub/discussions) no repositório. + +**Meu PR foi fechado sem merge. O que faço?** +> Leia o motivo no comentário de fechamento, corrija os pontos indicados e abra um novo PR referenciando o anterior. + ---
From 404b6aebd9a6634a008a3c4b3fa7284e3cb555e9 Mon Sep 17 00:00:00 2001 From: Luana Date: Tue, 19 May 2026 12:12:46 -0300 Subject: [PATCH 310/317] feat: adiciona dbt_project --- airflow_lappis/dags/dbt/mir/dbt_project.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/airflow_lappis/dags/dbt/mir/dbt_project.yml b/airflow_lappis/dags/dbt/mir/dbt_project.yml index 9fccdb95..98dcc400 100755 --- a/airflow_lappis/dags/dbt/mir/dbt_project.yml +++ b/airflow_lappis/dags/dbt/mir/dbt_project.yml @@ -18,7 +18,7 @@ clean-targets: models: mir: - +database: analytics + +database: postgres metadata: +materialized: incremental +schema: metadata @@ -31,6 +31,10 @@ models: empenhos_ted_dbt: +materialized: table +schema: siafi_dbt + siconv_dbt: + +materialized: table + +schema: siconv_dbt + on-run-start: From b750a3d2d6955f8ff6a2bfbe914d77e147698b44 Mon Sep 17 00:00:00 2001 From: Luana Date: Tue, 19 May 2026 12:30:04 -0300 Subject: [PATCH 311/317] =?UTF-8?q?fix:=20corre=C3=A7=C3=A3o=20do=20databa?= =?UTF-8?q?se?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- airflow_lappis/dags/dbt/mir/dbt_project.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airflow_lappis/dags/dbt/mir/dbt_project.yml b/airflow_lappis/dags/dbt/mir/dbt_project.yml index 98dcc400..7a65b43b 100755 --- a/airflow_lappis/dags/dbt/mir/dbt_project.yml +++ b/airflow_lappis/dags/dbt/mir/dbt_project.yml @@ -18,7 +18,7 @@ clean-targets: models: mir: - +database: postgres + +database: analytics metadata: +materialized: incremental +schema: metadata From 880f7dc2a1fb2ce1258380d8870f5e983c2c7451 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Tue, 19 May 2026 12:51:49 -0300 Subject: [PATCH 312/317] =?UTF-8?q?feat:=20adiciona=20novas=20features=20i?= =?UTF-8?q?ngest=C3=A3o=20emendas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mir/ne_tesouro_emendas_mir_ingest_dag.py | 53 ++++++++++--------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/ne_tesouro_emendas_mir_ingest_dag.py b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/ne_tesouro_emendas_mir_ingest_dag.py index f03e90a2..1feaad6f 100644 --- a/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/ne_tesouro_emendas_mir_ingest_dag.py +++ b/airflow_lappis/dags/data_ingest/tesouro_gerencial/mir/ne_tesouro_emendas_mir_ingest_dag.py @@ -30,31 +30,34 @@ 5: "acao_governo_descricao", 6: "autor_emendas_orcamento", 7: "autor_emendas_orcamento_descricao", - 8: "uf_pt", - 9: "uf_pt_descricao", - 10: "municipio_pt", - 11: "ne_ccor", - 12: "ne_num_processo", - 13: "ne_info_complementar", - 14: "ne_ccor_descricao", - 15: "doc_observacao", - 16: "grupo_despesa", - 17: "grupo_despesa_descricao", - 18: "natureza_despesa", - 19: "natureza_despesa_descricao", - 20: "modalidade_aplicacao", - 21: "modalidade_aplicacao_descricao", - 22: "ne_ccor_favorecido", - 23: "ne_ccor_favorecido_descricao", - 24: "ne_ccor_ano_emissao", - 25: "ptres", - 26: "item_informacao", - 27: "item_informacao_descricao", - 28: "despesas_empenhadas", - 29: "despesas_liquidadas", - 30: "despesas_pagas", - 31: "restos_a_pagar_inscritos", - 32: "restos_a_pagar_pagos", + 8: "localizador_gasto", + 9: "localizador_gasto_descricao", + 10: "regiao_pt", + 11: "uf_pt", + 12: "uf_pt_descricao", + 13: "municipio_pt", + 14: "ne_ccor", + 15: "ne_num_processo", + 16: "ne_info_complementar", + 17: "ne_ccor_descricao", + 18: "doc_observacao", + 19: "grupo_despesa", + 20: "grupo_despesa_descricao", + 21: "natureza_despesa", + 22: "natureza_despesa_descricao", + 23: "modalidade_aplicacao", + 24: "modalidade_aplicacao_descricao", + 25: "ne_ccor_favorecido", + 26: "ne_ccor_favorecido_descricao", + 27: "ne_ccor_ano_emissao", + 28: "ptres", + 29: "item_informacao", + 30: "item_informacao_descricao", + 31: "despesas_empenhadas", + 32: "despesas_liquidadas", + 33: "despesas_pagas", + 34: "restos_a_pagar_inscritos", + 35: "restos_a_pagar_pagos", } EMAIL_SUBJECT = "notas_de_empenhos_emendas_parlamentares" From cefb9f7d007173605dbee9bb41395291631627b7 Mon Sep 17 00:00:00 2001 From: Tiago Santos Bittencourt Date: Tue, 19 May 2026 13:22:28 -0300 Subject: [PATCH 313/317] feat: features de emendas passadas nas camandas --- .../dags/dbt/mir/models/emendas_dbt/bronze/schema.yml | 6 ++++++ .../dags/dbt/mir/models/emendas_dbt/bronze/tg_emendas.sql | 3 +++ .../dbt/mir/models/emendas_dbt/silver/emendas_partidos.sql | 6 ++++++ .../dags/dbt/mir/models/emendas_dbt/silver/schema.yml | 6 ++++++ .../dbt/mir/models/siconv_dbt/silver/emendas_convenio.sql | 3 +++ .../dags/dbt/mir/models/siconv_dbt/silver/schema.yaml | 6 ++++++ 6 files changed, 30 insertions(+) diff --git a/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/schema.yml b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/schema.yml index 7468e6c8..f04608e2 100644 --- a/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/schema.yml +++ b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/schema.yml @@ -1076,6 +1076,12 @@ models: description: "Ano de emissão da NE/CCOR." - name: ptres description: "PTRES associado à despesa da emenda." + - name: localizador_gasto + description: "Código do localizador de gasto associado à programação orçamentária." + - name: localizador_gasto_descricao + description: "Descrição do localizador de gasto." + - name: regiao_pt + description: "Região associada ao programa de trabalho (PT) da emenda." - name: item_informacao description: "Código do item de informação orçamentária." - name: item_informacao_descricao diff --git a/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/tg_emendas.sql b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/tg_emendas.sql index 9373af6a..8a36a013 100644 --- a/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/tg_emendas.sql +++ b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/bronze/tg_emendas.sql @@ -21,6 +21,9 @@ with ) ) ) as autor_emendas_orcamento_nome, + localizador_gasto::text as localizador_gasto, + localizador_gasto_descricao::text as localizador_gasto_descricao, + regiao_pt::text as regiao_pt, uf_pt::text as uf_pt, uf_pt_descricao::text as uf_pt_descricao, municipio_pt::text as municipio_pt, diff --git a/airflow_lappis/dags/dbt/mir/models/emendas_dbt/silver/emendas_partidos.sql b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/silver/emendas_partidos.sql index 1a57629b..b690d490 100644 --- a/airflow_lappis/dags/dbt/mir/models/emendas_dbt/silver/emendas_partidos.sql +++ b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/silver/emendas_partidos.sql @@ -27,6 +27,9 @@ cruzamento_bruto AS ( e.acao_governo AS codigo_acao_ajustada, e.acao_governo_descricao AS acao_ajustada, e.autor_emendas_orcamento_descricao, + e.localizador_gasto AS localizador_gasto, + e.localizador_gasto_descricao AS localizador_gasto_descricao, + e.regiao_pt AS regiao_pt, e.uf_pt AS uf, e.uf_pt_descricao AS uf_descricao, e.municipio_pt AS municipio, @@ -113,6 +116,9 @@ SELECT acao_ajustada, autor_emendas_orcamento_descricao, autor_emendas_orcamento_nome, + localizador_gasto, + localizador_gasto_descricao, + regiao_pt, uf, uf_descricao, municipio, diff --git a/airflow_lappis/dags/dbt/mir/models/emendas_dbt/silver/schema.yml b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/silver/schema.yml index cb8e2e9a..ecf8a240 100644 --- a/airflow_lappis/dags/dbt/mir/models/emendas_dbt/silver/schema.yml +++ b/airflow_lappis/dags/dbt/mir/models/emendas_dbt/silver/schema.yml @@ -231,6 +231,12 @@ models: description: Marcador de referência em representação anual do dia inicial do empenho do tesouro reportado. - name: ptres description: Programa de Trabalho Resumido - indicador consolidado contábil da União no sistema do SIAFI. + - name: localizador_gasto + description: Código do localizador de gasto associado à programação orçamentária. + - name: localizador_gasto_descricao + description: Descrição do localizador de gasto. + - name: regiao_pt + description: Região associada ao programa de trabalho (PT) da emenda. - name: item_informacao description: Hash ou item identificatório especial acoplado das métricas adicionais requeridas pelo processo contábil. - name: item_informacao_descricao diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/emendas_convenio.sql b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/emendas_convenio.sql index cce0a28f..880bc5ee 100644 --- a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/emendas_convenio.sql +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/emendas_convenio.sql @@ -23,6 +23,9 @@ select e.acao_ajustada, e.autor_emendas_orcamento_descricao, e.autor_emendas_orcamento_nome, + e.localizador_gasto, + e.localizador_gasto_descricao, + e.regiao_pt, e.uf, e.uf_descricao, e.municipio, diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/schema.yaml b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/schema.yaml index fce5ee0c..f4136cbc 100644 --- a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/schema.yaml +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/schema.yaml @@ -152,6 +152,12 @@ models: description: "Valor total do plano de ação." - name: tx_situacao_plano_acao description: "Situação do plano de ação." + - name: localizador_gasto + description: "Código do localizador de gasto associado à programação orçamentária." + - name: localizador_gasto_descricao + description: "Descrição do localizador de gasto." + - name: regiao_pt + description: "Região associada ao programa de trabalho (PT) da emenda." - name: dt_ingest description: "Timestamp de ingestão dos dados." tests: From d7b1afef4f51543f81b38c80be30fd8fe75cab0f Mon Sep 17 00:00:00 2001 From: arthurpalhares1 Date: Tue, 19 May 2026 15:02:40 -0300 Subject: [PATCH 314/317] Revert "Doc/guia de contribuicao" --- CONTRIBUTING.md | 385 ------------------------------------------------ 1 file changed, 385 deletions(-) delete mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 448baeaf..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,385 +0,0 @@ -# Guia de Contribuição — Gov Hub BR - -Antes de começar, obrigado por considerar contribuir com o **Gov Hub BR**! - -O GovHub BR é uma plataforma open-source com o propósito de transformar dados públicos em ativos estratégicos para a administração pública e a sociedade. Toda contribuição — seja código, pipelines de dados, documentação, ideias ou feedback — é bem-vinda. - ---- - -## Índice - -- [Código de Conduta](#código-de-conduta) -- [Estrutura do Projeto](#estrutura-do-projeto) -- [Configurando o Ambiente Local](#configurando-o-ambiente-local) -- [Convenção de Branches](#convenção-de-branches) -- [Padrão de Commits](#padrão-de-commits) -- [Fluxo de Pull Request](#fluxo-de-pull-request) -- [Como Pegar ou Atribuir Issues](#como-pegar-ou-atribuir-issues) -- [Processo de Code Review](#processo-de-code-review) -- [Executando os Testes](#executando-os-testes) -- [Padrões de Código e Lint](#padrões-de-código-e-lint) -- [Boas Práticas Gerais](#boas-práticas-gerais) - ---- -## Código de Conduta -Por favor, leia nosso [Código de Conduta](CODE_OF_CONDUCT.md). Ele está em vigor o tempo todo. Esperamos que seja respeitado por todos que contribuem para este projeto. Comportamentos inadequados não serão tolerados. - -## Estrutura do Projeto - -``` -. -├── airflow/ -│ ├── dags/ -│ └── plugins/ -├── dbt/ -│ └── models/ -├── jupyter/ -│ └── notebooks/ -├── superset/ -│ └── dashboards/ -├── docker-compose.yml -├── Makefile -├── CONTRIBUTING.md -└── README.md -``` - -> Consulte a [documentação de arquitetura](https://gov-hub.io/govhub/documentacao/arquitetura/) para entender o fluxo completo dos dados. - ---- - -## Configurando o Ambiente Local - -### Pré-requisitos - -- [Docker](https://docs.docker.com/get-docker/) e [Docker Compose](https://docs.docker.com/compose/) -- [Git](https://git-scm.com/) >= 2.x -- Python = 3.11 -- Make -- Acesso às credenciais dos sistemas estruturantes (quando necessário) — veja o [guia de credenciais](https://gov-hub.io/govhub/documentacao/tutoriais/sistemas-estruturantes/acesso-apis-siafi-siape/) - -### Passo a passo - -#### 1. Faça o fork pelo GitHub e clone o seu fork -```bash -git clone https://github.com//data-application-gov-hub.git -cd data-application-gov-hub -``` - -#### 2. Adicione o repositório original como remote "upstream" -```bash -git remote add upstream https://github.com/GovHub-br/data-application-gov-hub.git -``` - -#### 3. Execute a configuração usando Make: -```bash -make setup -``` - -Isso irá: -- Criar os ambientes virtuais necessários -- Instalar as dependências -- Configurar os hooks de pre-commit -- Preparar o ambiente de desenvolvimento - -#### 4. Copie as variáveis de ambiente e configure conforme necessário - -```bash -cp local.env .env -``` - -#### 5. Suba o ambiente com Docker Compose -```bash -docker compose up -d -``` - -#### 6. Acesse os serviços locais -```bash -# Apache Airflow: http://localhost:8080 -# Apache Superset: http://localhost:8088 -# Jupyter: http://localhost:8888 -``` - -Para instruções detalhadas de cada componente, consulte a [documentação de instalação](https://gov-hub.io/govhub/documentacao/instalacao/). - ---- - -## Convenção de Branches - -Crie sua branch a partir de `main` com um nome descritivo seguindo o padrão: - -``` -/ -``` - -| Tipo | Quando usar | -|------|-------------| -| `feat` | Nova funcionalidade ou pipeline | -| `fix` | Correção de bug ou inconsistência de dados | -| `docs` | Alterações apenas em documentação | -| `refactor` | Refatoração sem mudança de comportamento | -| `ci` | Mudanças em CI/CD ou infraestrutura | -| `test` | Adição ou ajuste de testes (DBT ou unitários) | -| `chore` | Tarefas de manutenção gerais | - -**Exemplos:** - -```bash -git checkout -b feat/integracao-siafi-despesas -git checkout -b fix/corrigir-modelo-silver-servidores -git checkout -b docs/atualizar-dicionario-siape -git checkout -b ci/ajustar-pipeline-kubernetes -``` - ---- - -## Padrão de Commits - -As mensagens de commit devem seguir o padrão [Conventional Commits](https://www.conventionalcommits.org/pt-br/), conforme adotado pelo projeto. - -### Formato - -``` -(): -``` - -### Tipos aceitos - -| Tipo | Descrição | -|------|-----------| -| `feat` | Nova funcionalidade ou novo modelo/pipeline | -| `fix` | Correção de bug ou erro em transformação | -| `docs` | Documentação (MkDocs, docstrings, README) | -| `ci` | Integração contínua, Docker, Kubernetes, Airflow | -| `refactor` | Melhoria de código sem alteração de comportamento | -| `test` | Testes DBT (`schema.yml`) ou testes unitários | -| `chore` | Manutenção geral, atualização de dependências | -| `perf` | Melhoria de performance em queries ou pipelines | - -### Exemplos - -```bash -# Nova funcionalidade -git commit -m "feat(dbt): adicionar modelo gold de execução orçamentária por UG" - -# Correção de bug -git commit -m "fix(dag): corrigir timeout na DAG de ingestão do SIAPE" - -# Documentação -git commit -m "docs: adicionar dicionário de dados para domínio de pessoal" - -# CI/Infraestrutura -git commit -m "ci: ajustar configuração do Astronomer Cosmos para DBT 1.8" - -# Referenciando issue -git commit -m "feat(dbt): criar snapshot de cargos e funções - -Closes #42" -``` - -Quando necessário, utilize a descrição estendida do commit para detalhar motivações, impactos e decisões técnicas importantes. Isso facilita o entendimento histórico das mudanças e contribui para uma base de código mais sustentável e auditável. - ---- - -## Fluxo de Pull Request - -``` -fork → branch → commits → push → Pull Request → review → merge -``` - -### Passo a passo - -```bash -# 1. Mantenha sua branch atualizada com o upstream antes de enviar -git fetch upstream -git rebase upstream/main - -# 2. Faça push da sua branch para o seu fork -git push origin feat/integracao-siafi-despesas - -# 3. Abra um Pull Request no GitHub apontando para a branch main do repositório principal -``` - -Antes de enviar, certifique-se de que sua alteração está funcionando corretamente, sem quebrar funcionalidades existentes, e que segue os padrões definidos pelo projeto. - -### Checklist antes de abrir o PR - -- [ ] As alterações funcionam corretamente no ambiente local (Docker Compose) -- [ ] Os testes DBT passam (`dbt test`) -- [ ] A branch está atualizada com `upstream/main` -- [ ] O título do PR segue o padrão Conventional Commits -- [ ] O PR referencia a issue relacionada (`Closes #`) -- [ ] Documentação atualizada, se aplicável -- [ ] Prints, logs ou exemplos de output incluídos quando relevante - -### Template de Pull Request - -Utilize o modelo disponível em `.github/PULL_REQUEST_TEMPLATE.md` ao abrir um PR: - -```markdown -## Descrição - - -## Tipo de mudança -- [ ] Nova funcionalidade / pipeline -- [ ] Correção de bug ou inconsistência de dados -- [ ] Refatoração de modelo DBT -- [ ] Documentação -- [ ] Infraestrutura / CI -- [ ] Outro: ___ - -## Issues relacionadas -Closes # - -## Como testar / validar - -1. -2. - -## Evidências - - -## Checklist -- [ ] Testes DBT adicionados/atualizados -- [ ] Documentação atualizada -- [ ] Sem dados sensíveis ou credenciais no código -``` - ---- - -## Como Pegar ou Atribuir Issues - -Toda solicitação de mudança, correção de bug ou sugestão de melhoria deve ser registrada por meio de uma issue, usando os templates disponíveis em `.github/ISSUE_TEMPLATE/`. Certifique-se de preencher todos os campos obrigatórios com informações precisas: contexto, impacto e possíveis caminhos de solução. - -1. Navegue até a aba [**Issues**](https://github.com/GovHub-br/govhub/issues) do repositório. -2. Filtre por labels como `good first issue` ou `help wanted` para começar. -3. Leia a descrição completa e verifique se a issue já tem alguém atribuído. -4. Comente na issue manifestando interesse: **"Posso trabalhar nessa issue?"**. -5. Aguarde a confirmação de um mantenedor antes de iniciar. - -> **Importante:** não abra PRs para issues já atribuídas a outro contribuidor sem combinar antes. - -### Templates disponíveis - -- `bug_report.md` — erros em modelos DBT, DAGs ou infraestrutura -- `feature_request.md` — novas integrações ou funcionalidades -- `documentation.md` — melhorias na documentação ou dicionário de dados - ---- - -## Processo de Code Review - -### Para quem submete o PR - -- Responda a todos os comentários de revisão de forma objetiva. -- Implemente as mudanças solicitadas em novos commits (evite `push --force` após o início do review). -- Aguarde nova aprovação antes de solicitar merge. - -### Para quem faz o review - -- Seja construtivo e específico — explique o *porquê* da sugestão. -- Diferencie bloqueadores (`X`) de sugestões opcionais. -- Verifique especialmente: qualidade dos modelos DBT, linhagem de dados, testes de qualidade e impacto em pipelines existentes. -- O merge é responsabilidade dos mantenedores do projeto. - ---- - -## Executando os Testes - -### Testes DBT - -```bash -# Executar todos os testes de qualidade de dados -dbt test - -# Executar testes de um modelo específico -dbt test --select modelo_gold_orcamento - -# Compilar os modelos sem executar (validação de SQL) -dbt compile - -# Gerar e visualizar a documentação DBT -dbt docs generate -dbt docs serve -``` - -Consulte a documentação de [testes DBT](https://gov-hub.io/govhub/documentacao/tutoriais/dbt/testes/) para mais detalhes. -### -### Validação de DAGs (Airflow) - -```bash -# Verificar sintaxe de todas as DAGs -python -m pytest tests/ -v - -# Testar execução de uma DAG específica -airflow dags test -``` - -> Toda contribuição que adiciona ou altera modelos DBT deve incluir testes de qualidade no `schema.yml` correspondente (ex.: `not_null`, `unique`, `accepted_values`). - ---- - -## Padrões de Código e Lint - -### DBT / SQL - -- Siga a [Arquitetura Medallion](https://gov-hub.io/govhub/documentacao/tutoriais/dbt/arquitetura-medallion/) (bronze → silver → gold). -- Nomeie modelos com prefixo da camada: `bronze_`, `silver_`, `gold_`. -- Documente cada modelo e coluna no arquivo `schema.yml` correspondente. -- Use [macros DBT](https://gov-hub.io/govhub/documentacao/tutoriais/dbt/macros/) para lógica reutilizável. - -### Python (DAGs e scripts) - -```bash -# Instalar dependências de desenvolvimento -pip install -r requirements-dev.txt - -# Verificar lint -flake8 dags/ --max-line-length=120 - -# Formatar código -black dags/ -``` - -### Documentação (MkDocs) - -```bash -# Visualizar documentação localmente -mkdocs serve - -# Construir site estático -mkdocs build -``` - ---- - -## Boas Práticas Gerais - -- **PRs pequenos e focados:** um PR por funcionalidade ou correção facilita o review. -- **Sem credenciais no código:** nunca commite tokens, senhas ou chaves de API — use variáveis de ambiente. Veja o [guia de credenciais](https://gov-hub.io/govhub/documentacao/tutoriais/sistemas-estruturantes/acesso-apis-siafi-siape/). -- **Sem dados sensíveis:** não inclua dados reais de servidores públicos ou cidadãos em testes ou exemplos. -- **Documente junto ao código:** atualize `schema.yml` e a documentação no mesmo PR da mudança. -- **Compatibilidade:** valide que sua contribuição não quebra pipelines ou modelos existentes. -- **Novas dependências:** discuta em uma issue antes de adicionar novos pacotes ou serviços. -- **Idioma:** código e comentários técnicos em **inglês**; issues, PRs e documentação em **português**. - -## FAQ - -**Posso contribuir sem ter sido atribuído a uma issue?** -> Sim, para correções triviais como typos na documentação. Para mudanças em modelos DBT, DAGs ou infraestrutura, abra ou comente uma issue antes. - -**Não tenho acesso aos sistemas estruturantes (SIAFI, SIAPE). Posso contribuir?** -> Sim! Muitas contribuições não exigem acesso a APIs governamentais — documentação, testes DBT com dados sintéticos, melhorias de infraestrutura e dashboards no Superset são exemplos. - -**Onde posso tirar dúvidas técnicas sobre DBT, Airflow ou Superset?** -> Consulte os [tutoriais da documentação](https://gov-hub.io/govhub/documentacao/instalacao/) ou abra uma [Discussion](https://github.com/GovHub-br/govhub/discussions) no repositório. - -**Meu PR foi fechado sem merge. O que faço?** -> Leia o motivo no comentário de fechamento, corrija os pontos indicados e abra um novo PR referenciando o anterior. - ---- - -
- Feito com 💜 pela comunidade GovHub BR  ·  - Reportar problema  ·  - Documentação -
\ No newline at end of file From 3f2f05c73ded0c3b87dfa65b806a3efdbc2b3325 Mon Sep 17 00:00:00 2001 From: alvesingrid Date: Tue, 19 May 2026 16:59:31 -0300 Subject: [PATCH 315/317] feat: cruzamento das tabelas de proposta e convenio --- .../siconv_dbt/silver/proposta_convenio.sql | 89 +++++++++ .../mir/models/siconv_dbt/silver/schema.yaml | 169 ++++++++++++++++++ 2 files changed, 258 insertions(+) create mode 100644 airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/proposta_convenio.sql diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/proposta_convenio.sql b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/proposta_convenio.sql new file mode 100644 index 00000000..e2b83480 --- /dev/null +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/proposta_convenio.sql @@ -0,0 +1,89 @@ +{{ config(materialized="table") }} + +with + convenio as ( + select * + from {{ ref("convenio") }} + ), + proposta as ( + select * + from {{ ref("proposta") }} + ) + +select + -- Colunas do convênio + c.nr_convenio, + c.dia as dia_conv, + c.mes as mes_conv, + c.ano as ano_conv, + c.dia_assin_conv, + c.sit_convenio, + c.subsituacao_conv, + c.situacao_publicacao, + c.instrumento_ativo, + c.ind_opera_obtv, + c.nr_processo, + c.ug_emitente, + c.dia_publ_conv, + c.dia_inic_vigenc_conv, + c.dia_fim_vigenc_conv, + c.dia_fim_vigenc_original_conv, + c.dias_prest_contas, + c.dia_limite_prest_contas, + c.data_suspensiva, + c.data_retirada_suspensiva, + c.dias_clausula_suspensiva, + c.situacao_contratacao, + c.ind_assinado, + c.motivo_suspensao, + c.ind_foto, + c.qtde_convenios, + c.qtd_ta, + c.qtd_prorroga, + c.vl_global_conv, + c.vl_repasse_conv, + c.vl_contrapartida_conv, + c.vl_empenhado_conv, + c.vl_desembolsado_conv, + c.vl_saldo_reman_tesouro, + c.vl_saldo_reman_convenente, + c.vl_rendimento_aplicacao, + c.vl_ingresso_contrapartida, + c.vl_saldo_conta, + c.valor_global_original_conv, + + -- Colunas da proposta + p.id_proposta, + p.uf_proponente, + p.munic_proponente, + p.cod_munic_ibge, + p.cod_orgao_sup, + p.desc_orgao_sup, + p.natureza_juridica, + p.nr_proposta, + p.dia_proposta, + p.cod_orgao, + p.desc_orgao, + p.modalidade, + p.identif_proponente, + p.nm_proponente, + p.cep_proponente, + p.endereco_proponente, + p.bairro_proponente, + p.nm_banco, + p.situacao_conta, + p.situacao_projeto_basico, + p.sit_proposta, + p.dia_inic_vigencia_proposta, + p.dia_fim_vigencia_proposta, + p.objeto_proposta, + p.item_investimento, + p.enviada_mandataria, + p.nome_subtipo_proposta, + p.descricao_subtipo_proposta, + p.vl_global_prop, + p.vl_repasse_prop, + p.vl_contrapartida_prop +from convenio c +inner join proposta p on c.id_proposta = p.id_proposta +where p.modalidade in ('CONVENIO', 'TERMO DE FOMENTO') diff --git a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/schema.yaml b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/schema.yaml index f4136cbc..406f16c5 100644 --- a/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/schema.yaml +++ b/airflow_lappis/dags/dbt/mir/models/siconv_dbt/silver/schema.yaml @@ -172,4 +172,173 @@ models: - verificacao_tipagem: nome_tabela: 'siconv_dbt.emendas_convenio' nome_coluna: 'vl_global_conv' + tipo_esperado: 'numeric' + + - name: proposta_convenio + description: > + Tabela da camada silver que cruza convênios com propostas do SICONV, + filtrando apenas as modalidades CONVENIO e TERMO DE FOMENTO. + Utiliza o `id_proposta` como chave de junção entre as tabelas de convênio + e proposta da camada bronze. Somente convênios que possuem proposta + associada nas modalidades filtradas são incluídos (INNER JOIN). + meta: + tags: + - silver + columns: + - name: nr_convenio + description: "Número do convênio." + - name: dia_conv + description: "Dia de assinatura do convênio." + - name: mes_conv + description: "Mês de assinatura do convênio." + - name: ano_conv + description: "Ano de assinatura do convênio." + - name: dia_assin_conv + description: "Data de assinatura do convênio." + - name: sit_convenio + description: "Situação do convênio." + - name: subsituacao_conv + description: "Subsituação do convênio." + - name: situacao_publicacao + description: "Situação de publicação do convênio." + - name: instrumento_ativo + description: "Indica se o instrumento está ativo." + - name: ind_opera_obtv + description: "Indicador de operação OBTV." + - name: nr_processo + description: "Número do processo." + - name: ug_emitente + description: "Código da UG emitente." + - name: dia_publ_conv + description: "Data de publicação do convênio." + - name: dia_inic_vigenc_conv + description: "Data de início da vigência do convênio." + - name: dia_fim_vigenc_conv + description: "Data de fim da vigência do convênio." + - name: dia_fim_vigenc_original_conv + description: "Data de fim da vigência original do convênio." + - name: dias_prest_contas + description: "Prazo em dias para prestação de contas." + - name: dia_limite_prest_contas + description: "Data limite para prestação de contas." + - name: data_suspensiva + description: "Data da cláusula suspensiva." + - name: data_retirada_suspensiva + description: "Data de retirada da cláusula suspensiva." + - name: dias_clausula_suspensiva + description: "Dias de cláusula suspensiva." + - name: situacao_contratacao + description: "Situação da contratação." + - name: ind_assinado + description: "Indica se o convênio foi assinado." + - name: motivo_suspensao + description: "Motivo da suspensão do convênio." + - name: ind_foto + description: "Indica se há foto associada." + - name: qtde_convenios + description: "Quantidade de convênios." + - name: qtd_ta + description: "Quantidade de termos aditivos." + - name: qtd_prorroga + description: "Quantidade de prorrogações." + - name: vl_global_conv + description: "Valor global do convênio." + - name: vl_repasse_conv + description: "Valor de repasse do convênio." + - name: vl_contrapartida_conv + description: "Valor de contrapartida do convênio." + - name: vl_empenhado_conv + description: "Valor empenhado do convênio." + - name: vl_desembolsado_conv + description: "Valor desembolsado do convênio." + - name: vl_saldo_reman_tesouro + description: "Valor do saldo remanescente do tesouro." + - name: vl_saldo_reman_convenente + description: "Valor do saldo remanescente do convenente." + - name: vl_rendimento_aplicacao + description: "Valor do rendimento de aplicação." + - name: vl_ingresso_contrapartida + description: "Valor de ingresso de contrapartida." + - name: vl_saldo_conta + description: "Valor do saldo em conta." + - name: valor_global_original_conv + description: "Valor global original do convênio." + - name: id_proposta + description: "Identificador único da proposta." + - name: uf_proponente + description: "Unidade Federativa do proponente." + - name: munic_proponente + description: "Município do proponente." + - name: cod_munic_ibge + description: "Código IBGE do município." + - name: cod_orgao_sup + description: "Código do órgão superior." + - name: desc_orgao_sup + description: "Descrição do órgão superior." + - name: natureza_juridica + description: "Natureza jurídica do proponente." + - name: nr_proposta + description: "Número da proposta." + - name: dia_proposta + description: "Data da proposta." + - name: cod_orgao + description: "Código do órgão." + - name: desc_orgao + description: "Descrição do órgão." + - name: modalidade + description: "Modalidade da proposta (CONVENIO ou TERMO DE FOMENTO)." + - name: identif_proponente + description: "Identificação do proponente (CNPJ/CPF)." + - name: nm_proponente + description: "Nome do proponente." + - name: cep_proponente + description: "CEP do proponente." + - name: endereco_proponente + description: "Endereço do proponente." + - name: bairro_proponente + description: "Bairro do proponente." + - name: nm_banco + description: "Nome do banco." + - name: situacao_conta + description: "Situação da conta bancária." + - name: situacao_projeto_basico + description: "Situação do projeto básico." + - name: sit_proposta + description: "Situação da proposta." + - name: dia_inic_vigencia_proposta + description: "Data de início da vigência da proposta." + - name: dia_fim_vigencia_proposta + description: "Data de fim da vigência da proposta." + - name: objeto_proposta + description: "Objeto da proposta." + - name: item_investimento + description: "Item de investimento." + - name: enviada_mandataria + description: "Indica se foi enviada à mandatária." + - name: nome_subtipo_proposta + description: "Nome do subtipo da proposta." + - name: descricao_subtipo_proposta + description: "Descrição do subtipo da proposta." + - name: vl_global_prop + description: "Valor global da proposta." + - name: vl_repasse_prop + description: "Valor de repasse da proposta." + - name: vl_contrapartida_prop + description: "Valor de contrapartida da proposta." + tests: + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.proposta_convenio' + nome_coluna: 'nr_convenio' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.proposta_convenio' + nome_coluna: 'id_proposta' + tipo_esperado: 'integer' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.proposta_convenio' + nome_coluna: 'vl_global_conv' + tipo_esperado: 'numeric' + - verificacao_tipagem: + nome_tabela: 'siconv_dbt.proposta_convenio' + nome_coluna: 'vl_global_prop' tipo_esperado: 'numeric' \ No newline at end of file From 4cfd3e7434529bbff515018eb675c984c6f34f2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Guimar=C3=A3es=20Borges?= <131381377+lcsgborges@users.noreply.github.com> Date: Wed, 27 May 2026 00:51:19 -0300 Subject: [PATCH 316/317] =?UTF-8?q?feat(ibge):=20ingest=C3=A3o=20Quilombol?= =?UTF-8?q?as=20=E2=80=94=20alfabetiza=C3=A7=C3=A3o=20e=20caracter=C3=ADst?= =?UTF-8?q?icas=20dos=20domic=C3=ADlios=20(#287)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(ibge): pipeline FTP Censo 2022 quilombolas alfabetização e domicílios * fix(ibge): corrige ReDoS no parser quilombolas e PK/upsert no Postgres --- .../quilombolas_alfabetizacao_ingest_dag.py | 168 +++++++ .../dags/dbt/ipea/models/sources.yml | 7 + airflow_lappis/helpers/quilombolas_parser.py | 459 ++++++++++++++++++ airflow_lappis/plugins/cliente_ibge.py | 126 ++++- airflow_lappis/plugins/cliente_postgres.py | 85 ++++ pyproject.toml | 1 + tests/test_quilombolas_parser.py | 160 ++++++ 7 files changed, 1001 insertions(+), 5 deletions(-) create mode 100644 airflow_lappis/dags/data_ingest/ibge/quilombolas_alfabetizacao_ingest_dag.py create mode 100644 airflow_lappis/helpers/quilombolas_parser.py create mode 100644 tests/test_quilombolas_parser.py diff --git a/airflow_lappis/dags/data_ingest/ibge/quilombolas_alfabetizacao_ingest_dag.py b/airflow_lappis/dags/data_ingest/ibge/quilombolas_alfabetizacao_ingest_dag.py new file mode 100644 index 00000000..c476dac7 --- /dev/null +++ b/airflow_lappis/dags/data_ingest/ibge/quilombolas_alfabetizacao_ingest_dag.py @@ -0,0 +1,168 @@ +""" +DAG de ingestão — Censo 2022: Quilombolas, alfabetização e características dos domicílios. + +Fonte FTP: +/Censos/Censo_Demografico_2022/Quilombolas_alfabetizacao_e_caracteristicas_dos_domicílios_Resultados_do_universo/ +""" + +import logging +from datetime import datetime, timedelta +from typing import Any + +import yaml +from airflow.decorators import dag, task +from airflow.models import Variable + +from cliente_ibge import ClienteIBGE +from cliente_postgres import ClientPostgresDB +from postgres_helpers import get_postgres_conn +from quilombolas_parser import ( + INDICES_FTP, + SUBPASTAS_DADOS, + extrair_chunks_de_excel, + parsear_arquivo_indice, + preparar_registros_insercao, +) +from schedule_loader import get_dynamic_schedule + +SCHEMA_DESTINO = "censo_demografico" +DATABASE_FTP = ( + "Quilombolas_alfabetizacao_e_caracteristicas_dos_domicílios_Resultados_do_universo" +) + + +def _obter_database_ftp() -> str: + config_str = Variable.get( + "ibge_quilombolas_config", + default_var=f'{{"database": "{DATABASE_FTP}"}}', + ) + return yaml.safe_load(config_str).get("database", DATABASE_FTP) + + +def _chunk_para_payload(chunk: Any) -> dict[str, Any]: + return { + "table_name": chunk.table_name, + "table_comment": chunk.table_comment, + "col_comments": chunk.col_comments, + "records": preparar_registros_insercao(chunk), + "primary_key": chunk.primary_key, + } + + +def _inserir_payloads(db: ClientPostgresDB, payloads: list[dict[str, Any]]) -> list[str]: + tabelas: list[str] = [] + for payload in payloads: + pk = payload["primary_key"] + db.insert_data( + data=payload["records"], + table_name=payload["table_name"], + schema=SCHEMA_DESTINO, + primary_key=pk, + conflict_fields=pk, + ) + db.apply_comments( + schema=SCHEMA_DESTINO, + table_name=payload["table_name"], + table_comment=payload.get("table_comment"), + column_comments=payload.get("col_comments"), + ) + tabelas.append(payload["table_name"]) + logging.info( + "Tabela criada/atualizada com comentários: %s.%s", + SCHEMA_DESTINO, + payload["table_name"], + ) + return tabelas + + +@dag( + schedule_interval=get_dynamic_schedule("quilombolas_censo_dag"), + start_date=datetime(2026, 1, 1), + catchup=False, + default_args={ + "owner": "Lucas Guimaraes", + "retries": 1, + "retry_delay": timedelta(minutes=5), + }, + tags=["quilombolas", "censo_demografico", "ibge", "alfabetizacao"], +) +def quilombolas_alfabetizacao_censo_dag() -> None: + """Extrai dados quilombolas do Censo 2022 via FTP e carrega em censo_demografico.""" + + @task + def listar_arquivos_dados() -> list[dict[str, str]]: + logging.info("[Task 1] Listando arquivos de dados no FTP...") + cliente = ClienteIBGE(database=_obter_database_ftp()) + arquivos = cliente.listar_arquivos_em_subpastas( + list(SUBPASTAS_DADOS), + formato_preferido="xlsx", + ) + logging.info("%d arquivo(s) de dados encontrado(s).", len(arquivos)) + return arquivos + + @task + def listar_arquivos_indices() -> list[dict[str, str]]: + logging.info("[Task 1b] Listando arquivos de índice no FTP...") + indices = ClienteIBGE(database=_obter_database_ftp()).listar_arquivos_texto( + INDICES_FTP + ) + logging.info("%d arquivo(s) de índice encontrado(s).", len(indices)) + return indices + + @task + def extrair_dados_excel(entrada: dict[str, str]) -> list[dict[str, Any]]: + subcaminho = entrada["subcaminho"] + arquivo = entrada["arquivo"] + logging.info("[Task 2] Extraindo %s/%s", subcaminho, arquivo) + + buffer = ClienteIBGE(database=_obter_database_ftp()).obter_conteudo_arquivo( + arquivo, subcaminho=subcaminho + ) + if not buffer: + raise ValueError(f"Falha ao baixar {subcaminho}/{arquivo}") + + chunks = extrair_chunks_de_excel(buffer, arquivo, subcaminho) + return [_chunk_para_payload(c) for c in chunks] + + @task + def extrair_indice(entrada: dict[str, str]) -> list[dict[str, Any]]: + subcaminho = entrada["subcaminho"] + arquivo = entrada["arquivo"] + logging.info("[Task 2b] Extraindo índice %s/%s", subcaminho, arquivo) + + conteudo = ClienteIBGE(database=_obter_database_ftp()).obter_conteudo_texto( + arquivo, subcaminho=subcaminho + ) + if not conteudo: + raise ValueError(f"Falha ao baixar índice {subcaminho}/{arquivo}") + + chunk = parsear_arquivo_indice(conteudo, subcaminho) + return [_chunk_para_payload(chunk)] + + @task + def inserir_chunks(payloads: list[dict[str, Any]]) -> list[str]: + if not payloads: + return [] + db = ClientPostgresDB(get_postgres_conn()) + return _inserir_payloads(db, payloads) + + @task + def consolidar_resultado( + tabelas_dados: list[list[str]], tabelas_indices: list[list[str]] + ) -> str: + total = sum(len(t) for t in tabelas_dados) + sum(len(t) for t in tabelas_indices) + return f"Processadas {total} tabelas no schema {SCHEMA_DESTINO}" + + lista_dados = listar_arquivos_dados() + lista_indices = listar_arquivos_indices() + + payloads_excel = extrair_dados_excel.expand(entrada=lista_dados) + payloads_indices = extrair_indice.expand(entrada=lista_indices) + + tabelas_dados = inserir_chunks.expand(payloads=payloads_excel) + tabelas_indices = inserir_chunks.expand(payloads=payloads_indices) + + consolidar_resultado(tabelas_dados, tabelas_indices) + + +quilombolas_alfabetizacao_censo_dag() diff --git a/airflow_lappis/dags/dbt/ipea/models/sources.yml b/airflow_lappis/dags/dbt/ipea/models/sources.yml index 221c0cc7..4af6b869 100644 --- a/airflow_lappis/dags/dbt/ipea/models/sources.yml +++ b/airflow_lappis/dags/dbt/ipea/models/sources.yml @@ -156,3 +156,10 @@ sources: - name: mulheres_tabela_15_br_gr_uf_mu - name: mulheres_tabela_16_br_gr_uf_mu - name: mulheres_tabela_17_br_gr_uf_mu + # Quilombolas — alfabetização e características dos domicílios (prefixo Q_A_C_D_) + - name: Q_A_C_D_apendice_01 + - name: Q_A_C_D_indice_tabelas_de_resultados + - name: Q_A_C_D_indice_tabelas_selecionadas + - name: Q_A_C_D_tabela_de_resultado_01 + - name: Q_A_C_D_tabela_de_resultado_02 + - name: Q_A_C_D_tabela_complementar_01 diff --git a/airflow_lappis/helpers/quilombolas_parser.py b/airflow_lappis/helpers/quilombolas_parser.py new file mode 100644 index 00000000..4daddff0 --- /dev/null +++ b/airflow_lappis/helpers/quilombolas_parser.py @@ -0,0 +1,459 @@ +""" +Parsing e limpeza dos arquivos do Censo 2022 — Quilombolas: +alfabetização e características dos domicílios (universo). +""" + +from __future__ import annotations + +import io +import logging +import re +import unicodedata +from dataclasses import dataclass, field + +import pandas as pd + +PREFIXO_TABELA = "Q_A_C_D_" +MAX_COL_LEN = 63 +VALORES_NULOS = frozenset({"nan", "none", ""}) + +SUBPASTAS_DADOS = ("Apendices", "Tabelas_de_resultados", "Tabelas_selecionadas") +INDICES_FTP: list[tuple[str, str]] = [ + ("Tabelas_de_resultados", "indice_de_tabelas_de_resultados.txt"), + ("Tabelas_selecionadas", "indice_de_tabelas_seleciondas.txt"), +] + + +@dataclass +class ChunkProcessado: + """Resultado do processamento de um bloco tabular.""" + + df: pd.DataFrame + table_name: str + table_comment: str + primary_key: list[str] = field(default_factory=list) + col_comments: dict[str, str] = field(default_factory=dict) + arquivo: str = "" + subcaminho: str = "" + sheet_name: str = "" + + +def _remover_acentos(texto: str) -> str: + return "".join( + c for c in unicodedata.normalize("NFD", texto) if unicodedata.category(c) != "Mn" + ) + + +def normalizar_nome_coluna(nome: str, idx: int) -> str: + """Converte descrição original em identificador PostgreSQL.""" + sem_acento = _remover_acentos(str(nome)) + limpo = re.sub( + r"[^\w%]", + "", + sem_acento.lower() + .replace("%", "_porcentagem") + .replace(" ", "_") + .replace("-", "_"), + ) + if not limpo or limpo == "none": + return f"coluna_{idx}" + if len(limpo) > MAX_COL_LEN: + limpo = limpo[:MAX_COL_LEN] + return limpo + + +def deduplicar_colunas(colunas: list[str]) -> list[str]: + contagem: dict[str, int] = {} + resultado: list[str] = [] + for col in colunas: + if col not in contagem: + contagem[col] = 0 + resultado.append(col) + continue + contagem[col] += 1 + sufixo = f"_{contagem[col]}" + if len(col) + len(sufixo) > MAX_COL_LEN: + base = col[: MAX_COL_LEN - len(sufixo)] + else: + base = col + resultado.append(f"{base}{sufixo}") + return resultado + + +def construir_nome_tabela(arquivo: str, sufixo: str = "") -> str: + """Gera nome no padrão Q_A_C_D_[nome_da_tabela].""" + stem = arquivo.rsplit(".", maxsplit=1)[0] + nome = re.sub(r"[^\w]", "_", _remover_acentos(stem).lower()) + nome = re.sub(r"_+", "_", nome).strip("_") + return f"{PREFIXO_TABELA}{nome}{sufixo}" + + +def construir_nome_tabela_indice(subpasta: str) -> str: + slug = re.sub(r"[^\w]", "_", _remover_acentos(subpasta).lower()) + slug = re.sub(r"_+", "_", slug).strip("_") + return f"{PREFIXO_TABELA}indice_{slug}" + + +def _identificar_chunks_horizontais(df_aba: pd.DataFrame) -> list[pd.DataFrame]: + cols_vazias = [ + i for i, col in enumerate(df_aba.columns) if df_aba[col].isnull().all() + ] + pontos = [-1] + cols_vazias + [len(df_aba.columns)] + chunks: list[pd.DataFrame] = [] + for i in range(len(pontos) - 1): + chunk = df_aba.iloc[:, pontos[i] + 1 : pontos[i + 1]].copy() + chunk = chunk.dropna(axis=1, how="all").dropna(axis=0, how="all") + if not chunk.empty and len(chunk.columns) > 1: + chunks.append(chunk.reset_index(drop=True)) + return chunks + + +def _texto_eh_linha_titulo(texto: str) -> bool: + """Detecta linha de título IBGE sem regex sujeito a backtracking (ReDoS).""" + lower = texto.lower().lstrip() + prefixos = ( + "tabela complementar", + "tabela de resultado", + "tabela de resultados", + "tabela", + "apêndice", + "apendice", + ) + for prefixo in prefixos: + if not lower.startswith(prefixo): + continue + sufixo = lower[len(prefixo) :].lstrip() + return bool(sufixo) and sufixo[0].isdigit() + return False + + +def _localizar_linha_titulo(df_raw: pd.DataFrame) -> int | None: + for idx in range(len(df_raw)): + texto = " ".join( + str(v).strip() + for v in df_raw.iloc[idx].tolist() + if str(v).strip().lower() not in VALORES_NULOS + ) + if _texto_eh_linha_titulo(texto): + return idx + return None + + +_PALAVRAS_CABECALHO_DIM = frozenset( + { + "estado", + "codigo", + "código", + "sigla", + "nome", + "territorio", + "território", + "unidade", + "status", + "simbolo", + "símbolo", + "significado", + "legenda", + } +) + + +def _eh_linha_cabecalho_dimensao(linha: pd.Series) -> bool: + textos = [ + str(v).strip().lower() + for v in linha.tolist() + if str(v).strip().lower() not in VALORES_NULOS + ] + if len(textos) < 2: + return False + return any( + any(palavra in texto for palavra in _PALAVRAS_CABECALHO_DIM) for texto in textos + ) + + +def _detectar_inicio_dados(df_raw: pd.DataFrame, idx_titulo: int | None) -> int | None: + mascara_num = df_raw.apply( + lambda r: pd.to_numeric(r, errors="coerce").notna().sum() > 1, axis=1 + ) + if mascara_num.any(): + return int(mascara_num.idxmax()) + + inicio_busca = (idx_titulo + 1) if idx_titulo is not None else 0 + for idx in range(inicio_busca, len(df_raw)): + linha = df_raw.iloc[idx] + textos = [ + str(v).strip() + for v in linha.tolist() + if str(v).strip().lower() not in VALORES_NULOS + ] + if len(textos) < 2: + continue + joined = " ".join(textos) + if re.search(r"fonte:|nota:|legenda", joined, re.IGNORECASE): + continue + if _eh_linha_cabecalho_dimensao(linha) and idx + 1 < len(df_raw): + return idx + 1 + return idx + return None + + +def _extrair_comentario_tabela(df_raw: pd.DataFrame, idx_titulo: int | None) -> str: + if idx_titulo is None: + return "" + texto = " ".join( + str(v).strip() + for v in df_raw.iloc[idx_titulo].tolist() + if str(v).strip().lower() not in VALORES_NULOS + ) + if " - " in texto: + return texto.split(" - ", maxsplit=1)[-1].strip() + return texto.strip() + + +def _construir_cabecalho_flat( + df_raw: pd.DataFrame, idx_titulo: int | None, idx_dados: int +) -> pd.DataFrame: + inicio = (idx_titulo + 1) if idx_titulo is not None else 0 + fim_cab = idx_dados + if idx_dados > 0 and _eh_linha_cabecalho_dimensao(df_raw.iloc[idx_dados - 1]): + fim_cab = idx_dados + cab = df_raw.iloc[inicio:fim_cab].copy().ffill(axis=1) + linhas_validas = [] + for row_idx in range(len(cab)): + valores = [ + str(v).strip() + for v in cab.iloc[row_idx].tolist() + if str(v).strip().lower() not in VALORES_NULOS + ] + if valores: + linhas_validas.append(row_idx) + return cab.iloc[linhas_validas] if linhas_validas else cab.iloc[0:0] + + +def _extrair_nome_coluna_flat(cabecalho: pd.DataFrame, col_idx: int) -> str: + pedacos: list[str] = [] + for row_idx in range(len(cabecalho)): + val = str(cabecalho.iloc[row_idx, col_idx]).strip() + if val.lower() in VALORES_NULOS: + continue + row_vals = cabecalho.iloc[row_idx].dropna().astype(str).str.strip() + unicos = [v for v in row_vals.unique() if v.lower() not in VALORES_NULOS] + if len(unicos) > 1: + pedacos.append(val.split(" - ")[-1].strip()) + elif len(unicos) == 1 and len(row_vals) == 1: + pedacos.append(val.split(" - ")[-1].strip()) + return "_".join(pedacos) if pedacos else f"coluna_{col_idx}" + + +_COLUNAS_AUDITORIA = frozenset({"dt_ingest", "nome_fonte", "subcaminho_fonte"}) + + +def _colunas_dados(df: pd.DataFrame) -> list[str]: + return [c for c in df.columns if c not in _COLUNAS_AUDITORIA] + + +def resolver_chave_primaria(df: pd.DataFrame) -> list[str]: + """ + Define a menor chave composta (prefixo à esquerda) que identifica cada linha. + + Nas tabelas IBGE as dimensões ficam à esquerda e as medidas à direita; usar + só as 3 primeiras colunas falha quando há vários territórios por UF. + """ + cols = _colunas_dados(df) + if not cols: + return [df.columns[0]] + if len(cols) == 1: + return cols + + for n in range(1, len(cols) + 1): + subset = cols[:n] + if df.drop_duplicates(subset=subset).shape[0] == len(df): + return subset + + return cols + + +def _limpar_dataframe(df: pd.DataFrame) -> pd.DataFrame: + if df.empty: + return df + col_dim = df.columns[0] + df = df.dropna(subset=[col_dim]) + df = df[ + ~df[col_dim] + .astype(str) + .str.contains(r"Fonte:|Nota:|Legenda", case=False, na=False) + ] + return df.drop_duplicates() + + +def processar_chunk_excel( + df_raw: pd.DataFrame, + arquivo: str, + subcaminho: str, + sheet_name: str, + sufixo: str = "", +) -> ChunkProcessado | None: + """Aplica flattening de cabeçalhos IBGE e prepara metadados de comentários.""" + idx_titulo = _localizar_linha_titulo(df_raw) + idx_dados = _detectar_inicio_dados(df_raw, idx_titulo) + if idx_dados is None: + logging.warning( + "[quilombolas_parser] Sem dados tabulares em %s / %s", arquivo, sheet_name + ) + return None + + cabecalho = _construir_cabecalho_flat(df_raw, idx_titulo, idx_dados) + nomes_originais = [ + _extrair_nome_coluna_flat(cabecalho, i) for i in range(len(df_raw.columns)) + ] + nomes_norm = deduplicar_colunas( + [normalizar_nome_coluna(n, i) for i, n in enumerate(nomes_originais)] + ) + + df = df_raw.iloc[idx_dados:].copy() + df.columns = nomes_norm + col_comments = { + norm: orig + for norm, orig in zip(nomes_norm, nomes_originais, strict=True) + if norm != orig or orig + } + df = _limpar_dataframe(df) + + colunas_fantasma = [c for c in df.columns if c.startswith("coluna_")] + if colunas_fantasma: + df = df.drop(columns=colunas_fantasma) + col_comments = { + k: v for k, v in col_comments.items() if k not in colunas_fantasma + } + + if df.empty or len(df.columns) == 0: + return None + + return ChunkProcessado( + df=df, + table_name=construir_nome_tabela(arquivo, sufixo=sufixo), + table_comment=_extrair_comentario_tabela(df_raw, idx_titulo), + primary_key=resolver_chave_primaria(df), + col_comments=col_comments, + arquivo=arquivo, + subcaminho=subcaminho, + sheet_name=sheet_name, + ) + + +def extrair_chunks_de_excel( + buffer: io.BytesIO, + arquivo: str, + subcaminho: str, +) -> list[ChunkProcessado]: + """Processa todas as abas e blocos horizontais de um arquivo Excel.""" + excel_file = pd.ExcelFile(buffer) + abas_validas = [ + a + for a in excel_file.sheet_names + if "gráfico" not in a.lower() + and "grafico" not in a.lower() + and "nota" not in a.lower() + ] + if not abas_validas: + abas_validas = [excel_file.sheet_names[0]] + + chunks: list[ChunkProcessado] = [] + for sheet_name in abas_validas: + df_aba = excel_file.parse(sheet_name, header=None) + partes = _identificar_chunks_horizontais(df_aba) + for idx, df_raw in enumerate(partes): + sufixo = f"_parte_{idx + 1}" if len(partes) > 1 else "" + resultado = processar_chunk_excel( + df_raw, arquivo, subcaminho, sheet_name, sufixo=sufixo + ) + if resultado: + chunks.append(resultado) + return chunks + + +_TIPOS_INDICE: tuple[tuple[str, str], ...] = ( + ("cartograma", "Cartograma"), + ("tabela", "Tabela"), + ("apêndice", "Apêndice"), + ("apendice", "Apêndice"), +) + + +def _parsear_linha_indice(linha: str) -> tuple[str, str, str] | None: + """Extrai tipo, número e descrição de uma linha de índice (sem ReDoS).""" + lower = linha.lower() + for chave, rotulo in _TIPOS_INDICE: + if not lower.startswith(chave): + continue + resto = linha[len(chave) :].lstrip() + numero_match = re.match(r"[0-9]+", resto) + if not numero_match: + return None + numero = numero_match.group(0) + descricao = resto[numero_match.end() :].lstrip() + if descricao and descricao[0] in "-–:": + descricao = descricao[1:].lstrip() + return rotulo, numero, descricao or linha + return None + + +def parsear_arquivo_indice(conteudo: str, subpasta: str) -> ChunkProcessado: + """Converte arquivo de índice TXT em tabela estruturada.""" + linhas = [ln.strip() for ln in conteudo.splitlines() if ln.strip()] + registros: list[dict[str, str]] = [] + for idx, linha in enumerate(linhas, start=1): + parsed = _parsear_linha_indice(linha) + if parsed: + tipo, numero, descricao = parsed + registros.append( + { + "ordem": str(idx), + "tipo": tipo, + "numero": numero, + "descricao": descricao, + "linha_original": linha, + } + ) + else: + registros.append( + { + "ordem": str(idx), + "tipo": "", + "numero": "", + "descricao": linha, + "linha_original": linha, + } + ) + + df = pd.DataFrame(registros) + return ChunkProcessado( + df=df, + table_name=construir_nome_tabela_indice(subpasta), + table_comment=f"Índice de tabelas — {subpasta.replace('_', ' ')}", + primary_key=["ordem"], + col_comments={ + "ordem": "Ordem da linha no arquivo de índice", + "tipo": "Tipo do item (Tabela, Cartograma, Apêndice)", + "numero": "Número do item no índice", + "descricao": "Descrição original do item", + "linha_original": "Texto original da linha no arquivo de índice", + }, + arquivo=f"indice_{subpasta.lower()}.txt", + subcaminho=subpasta, + sheet_name="indice", + ) + + +def preparar_registros_insercao(chunk: ChunkProcessado) -> list[dict[str, str]]: + """Adiciona colunas de auditoria, deduplica pela PK e serializa para inserção.""" + from datetime import datetime + + df = chunk.df.copy() + pk = chunk.primary_key or resolver_chave_primaria(df) + df = df.drop_duplicates(subset=pk, keep="last") + df["dt_ingest"] = datetime.now().isoformat() + df["nome_fonte"] = chunk.arquivo + df["subcaminho_fonte"] = chunk.subcaminho + return df.astype(str).to_dict(orient="records") + diff --git a/airflow_lappis/plugins/cliente_ibge.py b/airflow_lappis/plugins/cliente_ibge.py index 63a6f288..c70bcc91 100644 --- a/airflow_lappis/plugins/cliente_ibge.py +++ b/airflow_lappis/plugins/cliente_ibge.py @@ -20,7 +20,7 @@ def __init__(self, database: str) -> None: logging.info("[cliente_ibge] Inicializando conexão FTP com: %s", self.host) @contextmanager - def _conectar(self): + def _conectar(self, subcaminho: str = ""): """ Abre uma conexão FTP com o servidor público do IBGE. @@ -28,7 +28,7 @@ def _conectar(self): with self._conectar() as ftp: ftp.nlst() """ - full_path = f"{self.BASE_DIR.rstrip('/')}/{self.database.lstrip('/')}" + full_path = self._caminho_remoto(subcaminho) ftp = FTP(timeout=30) # NOSONAR try: ftp.connect(self.host) @@ -58,12 +58,113 @@ def listar_arquivos_alvo(self) -> list[str]: logging.error("[cliente_ibge] Erro ao listar arquivos: %s", exc) return [] - def obter_conteudo_arquivo(self, nome_arquivo: str) -> io.BytesIO | None: + def _caminho_remoto(self, subcaminho: str = "") -> str: + base = f"{self.BASE_DIR.rstrip('/')}/{self.database.lstrip('/')}" + if not subcaminho: + return base + return f"{base}/{subcaminho.strip('/')}" + + def listar_arquivos_em_subpastas( + self, + subpastas: list[str], + extensoes: tuple[str, ...] = (".xlsx", ".xls", ".csv"), + formato_preferido: str | None = "xlsx", + ) -> list[dict[str, str]]: + """ + Lista arquivos alvo em subpastas relativas ao diretório do tema. + + Retorna lista de dicts com chaves ``subcaminho`` e ``arquivo``. + Quando ``formato_preferido`` é informado, prioriza arquivos dessa + subpasta (ex.: ``xlsx`` em vez de ``ods``). + """ + resultado: list[dict[str, str]] = [] + try: + with self._conectar() as ftp: + base = self._caminho_remoto() + for subpasta in subpastas: + caminho = f"{base}/{subpasta.strip('/')}" + if formato_preferido: + caminho = f"{caminho}/{formato_preferido}" + try: + ftp.cwd(caminho) + except Exception as exc: + logging.warning( + "[cliente_ibge] Subpasta inacessível '%s': %s", + caminho, + exc, + ) + continue + + for nome in ftp.nlst(): + if nome in (".", ".."): + continue + if nome.endswith(extensoes): + resultado.append( + { + "subcaminho": ( + f"{subpasta.strip('/')}/{formato_preferido}" + if formato_preferido + else subpasta.strip("/") + ), + "arquivo": nome, + } + ) + + logging.info( + "[cliente_ibge] %d arquivo(s) em subpastas: %s", + len(resultado), + subpastas, + ) + return resultado + + except Exception as exc: + logging.error("[cliente_ibge] Erro ao listar subpastas: %s", exc) + return [] + + def listar_arquivos_texto( + self, entradas: list[tuple[str, str]] + ) -> list[dict[str, str]]: + """ + Lista arquivos de texto (índices) em subpastas. + + ``entradas`` é uma lista de tuplas ``(subpasta, nome_arquivo)``. + """ + encontrados: list[dict[str, str]] = [] + try: + with self._conectar() as ftp: + base = self._caminho_remoto() + for subpasta, nome_arquivo in entradas: + caminho = f"{base}/{subpasta.strip('/')}" + try: + ftp.cwd(caminho) + if nome_arquivo in ftp.nlst(): + encontrados.append( + { + "subcaminho": subpasta.strip("/"), + "arquivo": nome_arquivo, + } + ) + except Exception as exc: + logging.warning( + "[cliente_ibge] Índice não encontrado em '%s': %s", + caminho, + exc, + ) + return encontrados + except Exception as exc: + logging.error("[cliente_ibge] Erro ao listar índices: %s", exc) + return [] + + def obter_conteudo_arquivo( + self, nome_arquivo: str, subcaminho: str = "" + ) -> io.BytesIO | None: """Baixa um arquivo do FTP diretamente para memória.""" buffer = io.BytesIO() try: - with self._conectar() as ftp: - logging.info("[cliente_ibge] Baixando: %s", nome_arquivo) + with self._conectar(subcaminho=subcaminho) as ftp: + logging.info( + "[cliente_ibge] Baixando: %s/%s", subcaminho or ".", nome_arquivo + ) ftp.retrbinary(f"RETR {nome_arquivo}", buffer.write) buffer.seek(0) @@ -72,3 +173,18 @@ def obter_conteudo_arquivo(self, nome_arquivo: str) -> io.BytesIO | None: except Exception as exc: logging.error("[cliente_ibge] Erro ao baixar '%s': %s", nome_arquivo, exc) return None + + def obter_conteudo_texto( + self, nome_arquivo: str, subcaminho: str = "" + ) -> str | None: + """Baixa um arquivo de texto do FTP e retorna o conteúdo decodificado.""" + buffer = self.obter_conteudo_arquivo(nome_arquivo, subcaminho=subcaminho) + if not buffer: + return None + raw = buffer.read() + for encoding in ("utf-8", "latin-1", "cp1252"): + try: + return raw.decode(encoding) + except UnicodeDecodeError: + continue + return raw.decode("latin-1", errors="replace") diff --git a/airflow_lappis/plugins/cliente_postgres.py b/airflow_lappis/plugins/cliente_postgres.py index 9e4e0ce3..c30eb36b 100755 --- a/airflow_lappis/plugins/cliente_postgres.py +++ b/airflow_lappis/plugins/cliente_postgres.py @@ -1,4 +1,5 @@ import logging +import re from contextlib import contextmanager from typing import Any, Dict, List, Optional, Tuple import psycopg2 @@ -18,6 +19,11 @@ class ClientPostgresDB: def _get_column_type(value: Any) -> str: return ClientPostgresDB.TYPE_MAP.get(type(value), "TEXT") + @staticmethod + def _unique_index_name(table_name: str, columns: List[str]) -> str: + raw = f"uq_{table_name}_{'_'.join(columns)}" + return re.sub(r"[^\w]", "_", raw)[:63] + def _flatten_data(self, data: List[Dict[str, Any]]) -> List[Dict[str, Any]]: return list( map( @@ -123,6 +129,10 @@ def insert_data( column_probe, table_name, primary_key=primary_key, schema=schema, conn=conn ) self.alter_table(column_probe, table_name, schema=schema, conn=conn) + if conflict_fields: + self.ensure_unique_constraint( + schema, table_name, conflict_fields, conn=conn + ) values = [tuple(item.get(col) for col in columns) for item in flattened_data] @@ -356,6 +366,57 @@ def get_codigo_unidade(self) -> list[dict]: for row in rows ] + def ensure_unique_constraint( + self, + schema: str, + table_name: str, + columns: List[str], + conn=None, + ) -> None: + """ + Garante índice UNIQUE para ON CONFLICT quando a tabela já existia + sem a PK composta correta (CREATE TABLE IF NOT EXISTS não altera constraints). + """ + if not columns: + return + + index_name = self._unique_index_name(table_name, columns) + cols_sql = ", ".join(columns) + query = ( + f"CREATE UNIQUE INDEX IF NOT EXISTS {index_name} " + f"ON {schema}.{table_name} ({cols_sql});" + ) + + def _execute(connection): + with connection.cursor() as cursor: + try: + cursor.execute(query) + logging.info( + "[cliente_postgres.py] Unique index %s on %s.%s (%s)", + index_name, + schema, + table_name, + cols_sql, + ) + except psycopg2.Error as err: + logging.error( + "[cliente_postgres.py] Failed to create unique index on " + "%s.%s: %s", + schema, + table_name, + err, + ) + raise RuntimeError( + f"Failed to ensure unique constraint on {schema}.{table_name}" + ) from err + + if conn is not None: + _execute(conn) + else: + with self._connect() as new_conn: + _execute(new_conn) + new_conn.commit() + def execute_non_query(self, query: str) -> None: logging.info(f"[cliente_postgres.py] Executando non-query: {query}") with self._connect() as conn: @@ -370,6 +431,30 @@ def execute_non_query(self, query: str) -> None: ) raise RuntimeError("Erro ao executar comando SQL sem retorno") from e + def apply_comments( + self, + schema: str, + table_name: str, + table_comment: str | None = None, + column_comments: dict[str, str] | None = None, + ) -> None: + """Aplica COMMENT ON TABLE e COMMENT ON COLUMN no PostgreSQL.""" + queries: list[str] = [] + tabela = f"{schema}.{table_name}" + + if table_comment: + desc = table_comment.replace("'", "''") + queries.append(f"COMMENT ON TABLE {tabela} IS '{desc}';") + + for coluna, descricao in (column_comments or {}).items(): + if not descricao: + continue + desc = descricao.replace("'", "''") + queries.append(f"COMMENT ON COLUMN {tabela}.{coluna} IS '{desc}';") + + for query in queries: + self.execute_non_query(query) + def get_dashboard_kpis(self) -> Dict[str, int]: query = "SELECT kpi, valor FROM pessoas.kpis_servidores" with self._connect() as conn: diff --git a/pyproject.toml b/pyproject.toml index c67e52fe..2dca6ad5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -117,6 +117,7 @@ disable_error_code = ["arg-type", "attr-defined"] [tool.pytest.ini_options] testpaths = ["tests"] python_files = ["test_*.py"] +pythonpath = ["airflow_lappis", "airflow_lappis/helpers", "airflow_lappis/plugins"] [tool.sqlfmt] line_length = 90 diff --git a/tests/test_quilombolas_parser.py b/tests/test_quilombolas_parser.py new file mode 100644 index 00000000..095642f1 --- /dev/null +++ b/tests/test_quilombolas_parser.py @@ -0,0 +1,160 @@ +import pandas as pd + +from quilombolas_parser import ( + PREFIXO_TABELA, + construir_nome_tabela, + construir_nome_tabela_indice, + deduplicar_colunas, + normalizar_nome_coluna, + parsear_arquivo_indice, + preparar_registros_insercao, + processar_chunk_excel, + resolver_chave_primaria, +) + + +def test_construir_nome_tabela_prefixo() -> None: + assert construir_nome_tabela("Tabela_de_resultado_02.xlsx") == ( + f"{PREFIXO_TABELA}tabela_de_resultado_02" + ) + + +def test_construir_nome_tabela_indice() -> None: + assert construir_nome_tabela_indice("Tabelas_de_resultados") == ( + f"{PREFIXO_TABELA}indice_tabelas_de_resultados" + ) + + +def test_normalizar_nome_coluna_remove_acentos() -> None: + nome = normalizar_nome_coluna("Taxa de alfabetização (%)", 0) + assert "alfabetizacao" in nome + assert "porcentagem" in nome + + +def test_deduplicar_colunas() -> None: + assert deduplicar_colunas(["total", "total", "total"]) == [ + "total", + "total_1", + "total_2", + ] + + +def test_parsear_arquivo_indice() -> None: + conteudo = ( + "Tabela 1 - População residente - Brasil - 2022\n" + "Tabela 2 - População por UF - 2022\n" + ) + chunk = parsear_arquivo_indice(conteudo, "Tabelas_de_resultados") + assert chunk.table_name == f"{PREFIXO_TABELA}indice_tabelas_de_resultados" + assert len(chunk.df) == 2 + assert chunk.df.iloc[0]["numero"] == "1" + assert "População residente" in chunk.df.iloc[0]["descricao"] + + +def test_parsear_arquivo_indice_sem_hifen() -> None: + """Índice IBGE pode usar espaços duplos em vez de hífen.""" + conteudo = "Tabela 3 População por sexo - 2022\n" + chunk = parsear_arquivo_indice(conteudo, "Tabelas_selecionadas") + assert chunk.df.iloc[0]["numero"] == "3" + assert "População por sexo" in chunk.df.iloc[0]["descricao"] + + +def test_localizar_linha_titulo_tabela_complementar() -> None: + from quilombolas_parser import _localizar_linha_titulo + + df = pd.DataFrame( + [ + ["Censo Demográfico 2022"], + ["Tabela complementar 1 - Alfabetização - 2022"], + ["Brasil", "100"], + ] + ) + assert _localizar_linha_titulo(df) == 1 + + +def test_processar_chunk_excel_apendice() -> None: + """Simula layout do Apêndice 1 (tabela textual sem valores numéricos).""" + linhas = [ + ["Censo Demográfico 2022", None], + ["Quilombolas: tema", None], + ["Apêndice 1 - Territórios citados - Brasil - 2022", None], + ["Estado", "Território Quilombola"], + ["PA", "Cuxiu"], + ["PA", "Guajarauna"], + ] + df_raw = pd.DataFrame(linhas) + chunk = processar_chunk_excel( + df_raw, "Apendice_01.xlsx", "Apendices/xlsx", "Apendice 1" + ) + assert chunk is not None + assert chunk.table_name == f"{PREFIXO_TABELA}apendice_01" + assert list(chunk.df.columns)[0] == "estado" + assert len(chunk.df) == 2 + assert chunk.table_comment.startswith("Territórios") + + +def test_resolver_chave_primaria_territorios_por_uf() -> None: + """Vários territórios na mesma UF exigem PK além das 3 primeiras colunas.""" + df = pd.DataFrame( + [ + ["Titulado", "11", "RO", "Rondônia", "11001", "TQ A", "100", "50"], + ["Titulado", "11", "RO", "Rondônia", "11280", "TQ B", "200", "80"], + ], + columns=[ + "status_fundiario", + "unidade_da_federacao_codigo", + "unidade_da_federacao_sigla", + "unidade_da_federacao_nome", + "territorio_quilombola_codigo", + "territorio_quilombola_nome", + "populacao_residente_total", + "populacao_residente_quilombola", + ], + ) + pk = resolver_chave_primaria(df) + assert "territorio_quilombola_codigo" in pk + assert len(pk) <= 5 + assert df.drop_duplicates(subset=pk).shape[0] == len(df) + + +def test_preparar_registros_deduplica_por_pk() -> None: + from quilombolas_parser import ChunkProcessado + + chunk = ChunkProcessado( + df=pd.DataFrame( + [["PA", "Cuxiu"], ["PA", "Cuxiu"]], + columns=["estado", "territorio_quilombola"], + ), + table_name=f"{PREFIXO_TABELA}apendice_01", + table_comment="teste", + primary_key=["estado", "territorio_quilombola"], + ) + registros = preparar_registros_insercao(chunk) + assert len(registros) == 1 + + +def test_processar_chunk_excel_cabecalho_triplo() -> None: + """Simula cabeçalho hierárquico de tabela complementar.""" + linhas = [ + ["Censo Demográfico 2022"] * 3, + ["Quilombolas: tema"] * 3, + [None] * 3, + ["Tabela complementar 1 - Alfabetização por região - 2022"] * 3, + [None] * 3, + ["Região", "População 15+", "População 15+"], + [None, "Total", "Quilombolas"], + [None, None, None], + ["Brasil", "100", "10"], + ["Norte", "50", "5"], + ] + df_raw = pd.DataFrame(linhas) + chunk = processar_chunk_excel( + df_raw, + "Tabela_complementar_01.xlsx", + "Tabelas_selecionadas/xlsx", + "Tabela complementar 1", + ) + assert chunk is not None + assert "regiao" in chunk.df.columns[0] + assert len(chunk.df) == 2 + assert "total" in chunk.col_comments.get(chunk.df.columns[1], "").lower() From 9c53813c6fa86d8bdd3469dedd8113f10ace5bca Mon Sep 17 00:00:00 2001 From: gabrielsarcan Date: Wed, 27 May 2026 19:00:38 -0300 Subject: [PATCH 317/317] =?UTF-8?q?feat:=20Adiciona=20cliente=20MRV=20e=20?= =?UTF-8?q?testes=20unit=C3=A1rios=20com=20mocks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- airflow_lappis/plugins/cliente_mrv.py | 16 ++++++++ tests/test_cliente_mrv.py | 54 +++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 airflow_lappis/plugins/cliente_mrv.py create mode 100644 tests/test_cliente_mrv.py diff --git a/airflow_lappis/plugins/cliente_mrv.py b/airflow_lappis/plugins/cliente_mrv.py new file mode 100644 index 00000000..ecc88cc4 --- /dev/null +++ b/airflow_lappis/plugins/cliente_mrv.py @@ -0,0 +1,16 @@ +from airflow_lappis.plugins.cliente_base import ClienteBase +from typing import Optional, Dict, Any, Tuple +from http import HTTPStatus + +class ClienteMRV(ClienteBase): + """ + Cliente para integração com a API da MRV. + """ + def __init__(self, base_url: str = "https://api.mrv.com.br", headers: Optional[dict] = None) -> None: + super().__init__(base_url=base_url, headers=headers) + + def consultar_empreendimentos(self, params: Optional[Dict[str, Any]] = None) -> Tuple[HTTPStatus, Optional[dict | list]]: + """ + Consulta a lista de empreendimentos imobiliários da MRV. + """ + return self.request("GET", "/empreendimentos", params=params) diff --git a/tests/test_cliente_mrv.py b/tests/test_cliente_mrv.py new file mode 100644 index 00000000..761dcc31 --- /dev/null +++ b/tests/test_cliente_mrv.py @@ -0,0 +1,54 @@ +from unittest.mock import patch, MagicMock +from http import HTTPStatus +import httpx +import pytest + +from airflow_lappis.plugins.cliente_mrv import ClienteMRV + +class TestClienteMRV: + @pytest.fixture + def cliente_mrv(self): + return ClienteMRV() + + @patch("httpx.Client.request") + def test_consultar_empreendimentos_sucesso(self, mock_request, cliente_mrv): + # Configura o mock para retornar uma resposta de sucesso + mock_response = MagicMock(spec=httpx.Response) + mock_response.status_code = HTTPStatus.OK + mock_response.json.return_value = [{"id": 1, "nome": "Residencial Arvoredo", "cidade": "São Paulo"}] + mock_request.return_value = mock_response + + status, dados = cliente_mrv.consultar_empreendimentos(params={"cidade": "São Paulo"}) + + # Verifica se a chamada foi feita corretamente + mock_request.assert_called_once_with( + "GET", + "/empreendimentos", + params={"cidade": "São Paulo"}, + timeout=cliente_mrv.DEFAULT_TIMEOUT + ) + + assert status == HTTPStatus.OK + assert isinstance(dados, list) + assert len(dados) == 1 + assert dados[0]["nome"] == "Residencial Arvoredo" + + @patch("httpx.Client.request") + def test_consultar_empreendimentos_falha(self, mock_request, cliente_mrv): + # Simula uma falha na requisição (ex: erro 404) + mock_response = MagicMock(spec=httpx.Response) + mock_response.status_code = HTTPStatus.NOT_FOUND + + # raise_for_status deve levantar exceção para simular o comportamento real do httpx + def raise_for_status_mock(): + raise httpx.HTTPStatusError("Not Found", request=MagicMock(), response=mock_response) + + mock_response.raise_for_status.side_effect = raise_for_status_mock + mock_request.return_value = mock_response + + # Executa e espera que levante a exceção mapeada no ClienteBase + with pytest.raises(Exception, match="API failed after the maximum number of attempts!"): + cliente_mrv.consultar_empreendimentos() + + # Verifica que ocorreu as tentativas de retentativa definidas em ClienteBase + assert mock_request.call_count == cliente_mrv.DEFAULT_MAX_RETRIES