Este documento detalha a arquitetura de dados e o processo de ETL (Extração, Transformação e Carga) para a criação de um módulo Python OpenSource. O objetivo é processar os arquivos mensais do SINAPI, consolidando os dados em um banco de dados PostgreSQL de forma robusta, normalizada e com total rastreabilidade histórica.
A estrutura resultante permitirá que a comunidade de engenharia e arquitetura realize consultas complexas para orçamentação, planejamento e análise histórica, seja através de uma API ou acessando o banco de dados localmente.
O ecossistema de dados do SINAPI é composto por dois arquivos principais, que devem ser processados em conjunto para garantir a consistência e a integridade do banco de dados:
SINAPI_Referência_AAAA_MM.xlsx: Arquivo principal contendo os catálogos de preços, custos e a estrutura analítica das composições para o mês de referência.SINAPI_manutencoes_AAAA_MM.xlsx: Arquivo de suporte que detalha todo o histórico de alterações (ativações, desativações, mudanças de descrição) dos insumos e composições. É a fonte da verdade para o ciclo de vida de cada item.
O modelo é projetado para máxima integridade, performance e clareza, separando entidades de catálogo, dados de série histórica, suas relações estruturais e o histórico de eventos.
Armazenam a descrição única e o estado atual de cada insumo e composição.
| Coluna | Tipo | Restrições/Descrição |
|---|---|---|
codigo |
INTEGER |
Chave Primária (PK) |
descricao |
TEXT |
Descrição completa do insumo. |
unidade |
VARCHAR |
Unidade de medida (UN, M2, M3, KG). |
classificacao |
TEXT |
Classificação hierárquica do insumo. |
status |
VARCHAR |
'ATIVO' ou 'DESATIVADO'. Controlado pelo ETL de manutenções. |
| Coluna | Tipo | Restrições/Descrição |
|---|---|---|
codigo |
INTEGER |
Chave Primária (PK) |
descricao |
TEXT |
Descrição completa da composição. |
unidade |
VARCHAR |
Unidade de medida (UN, M2, M3). |
grupo |
VARCHAR |
Grupo ao qual a composição pertence. |
status |
VARCHAR |
'ATIVO' ou 'DESATIVADO'. Controlado pelo ETL de manutenções. |
Recebem novos registros a cada mês, construindo o histórico de preços e custos.
| Coluna | Tipo | Restrições/Descrição |
|---|---|---|
insumo_codigo |
INTEGER |
FK -> insumos.codigo |
uf |
CHAR(2) |
Unidade Federativa. |
data_referencia |
DATE |
Primeiro dia do mês de referência. |
preco_mediano |
NUMERIC |
Preço do insumo na UF/Data/Regime. |
regime |
VARCHAR |
'NAO_DESONERADO', 'DESONERADO', 'SEM_ENCARGOS'. |
| PK Composta | (insumo_codigo, uf, data_referencia, regime) |
| Coluna | Tipo | Restrições/Descrição |
|---|---|---|
composicao_codigo |
INTEGER |
FK -> composicoes.codigo |
uf |
CHAR(2) |
Unidade Federativa. |
data_referencia |
DATE |
Primeiro dia do mês de referência. |
custo_total |
NUMERIC |
Custo da composição na UF/Data/Regime. |
regime |
VARCHAR |
'NAO_DESONERADO', 'DESONERADO', 'SEM_ENCARGOS'. |
| PK Composta | (composicao_codigo, uf, data_referencia, regime) |
Modelam a estrutura hierárquica das composições. Devem ser totalmente recarregadas a cada mês para refletir a estrutura mais atual.
| Coluna | Tipo | Restrições/Descrição |
|---|---|---|
composicao_pai_codigo |
INTEGER |
FK -> composicoes.codigo |
insumo_filho_codigo |
INTEGER |
FK -> insumos.codigo |
coeficiente |
NUMERIC |
Coeficiente de consumo do insumo. |
| PK Composta | (composicao_pai_codigo, insumo_filho_codigo) |
| Coluna | Tipo | Restrições/Descrição |
|---|---|---|
composicao_pai_codigo |
INTEGER |
FK -> composicoes.codigo |
composicao_filho_codigo |
INTEGER |
FK -> composicoes.codigo |
coeficiente |
NUMERIC |
Coeficiente de consumo da subcomposição. |
| PK Composta | (composicao_pai_codigo, composicao_filho_codigo) |
Esta tabela é o log imutável de todas as mudanças ocorridas nos itens do SINAPI.
| Coluna | Tipo | Restrições/Descrição |
|---|---|---|
item_codigo |
INTEGER |
Código do Insumo ou Composição. |
tipo_item |
VARCHAR |
'INSUMO' ou 'COMPOSICAO'. |
data_referencia |
DATE |
Data do evento de manutenção (primeiro dia do mês). |
tipo_manutencao |
TEXT |
Descrição da manutenção realizada (Ex: 'DESATIVAÇÃO'). |
descricao_item |
TEXT |
Descrição do item no momento do evento. |
| PK Composta | (item_codigo, tipo_item, data_referencia, tipo_manutencao) |
Para facilitar a consulta de todos os itens de uma composição (sejam insumos ou subcomposições) sem a necessidade de acessar duas tabelas, uma VIEW deve ser criada no banco de dados.
CREATE OR REPLACE VIEW vw_composicao_itens_unificados AS
SELECT
composicao_pai_codigo,
insumo_filho_codigo AS item_codigo,
'INSUMO' AS tipo_item,
coeficiente
FROM
composicao_insumos
UNION ALL
SELECT
composicao_pai_codigo,
composicao_filho_codigo AS item_codigo,
'COMPOSICAO' AS tipo_item,
coeficiente
FROM
composicao_subcomposicoes;O fluxo de execução foi projetado para adotar uma abordagem "Manutenções Primeiro", garantindo a máxima consistência dos dados.
- Caminho do Arquivo de Referência:
path/to/SINAPI_Referência_AAAA_MM.xlsx - Caminho do Arquivo de Manutenções:
path/to/SINAPI_manutencoes_AAAA_MM.xlsx - Data de Referência: Derivada do nome do arquivo (ex:
2025-07-01). - String de Conexão com o Banco de Dados.
Esta fase estabelece a fonte da verdade sobre o status de cada item.
-
Extração:
- Carregar a planilha
Manutençõesdo arquivoSINAPI_manutencoes_AAAA_MM.xlsx. - Atenção: O cabeçalho está na linha 6, portanto, use
header=5na leitura.
- Carregar a planilha
-
Transformação:
- Renomear as colunas para o padrão do banco de dados (ex:
Código->item_codigo). - Converter a coluna
Referência(formatoMM/AAAA) para umDATEválido (primeiro dia do mês, ex:07/2025->2025-07-01). - Limpar e padronizar os dados textuais.
- Renomear as colunas para o padrão do banco de dados (ex:
-
Carga:
- Inserir os dados transformados na tabela
manutencoes_historico. - Utilizar uma cláusula
ON CONFLICT DO NOTHINGna chave primária composta para evitar a duplicação de registros históricos caso o ETL seja re-executado.
- Inserir os dados transformados na tabela
Esta fase utiliza os dados carregados na Fase 1 para atualizar o estado atual dos itens.
- Lógica de Atualização: Executar um script (em Python/SQL) que:
- Para cada item (
código,tipo) presente na tabelamanutencoes_historico, identifique a manutenção mais recente (últimadata_referencia). - Verifique se o
tipo_manutencaodessa última entrada indica uma desativação (ex:tipo_manutencao ILIKE '%DESATIVAÇÃO%'). - Se for uma desativação, executar um
UPDATEna tabela correspondente (insumosoucomposicoes), ajustando o campostatuspara'DESATIVADO'.
- Para cada item (
Esta fase processa o arquivo principal do SINAPI, operando sobre catálogos cujo status já foi sincronizado.
-
Extração:
- Carregar as planilhas de referência (
ISD,ICD,ISE,CSD,CCD,CSE,Analítico) do arquivoSINAPI_Referência_AAAA_MM.xlsx. - Atenção: O cabeçalho dos dados começa na linha 9, portanto, use
header=9.
- Carregar as planilhas de referência (
-
Transformação:
- Enriquecimento de Contexto (Regime): Adicionar uma coluna
regimea cada DataFrame de preço/custo, mapeando o nome da planilha para o valor ('NAO_DESONERADO','DESONERADO','SEM_ENCARGOS'). - Unpivot (Melt): Transformar os DataFrames do formato "largo" (UFs em colunas) para o formato "longo" (UFs em linhas).
- Consolidação: Unir os DataFrames de mesmo tipo (insumos com insumos, composições com composições).
- Separação dos Dados: A partir dos DataFrames consolidados, criar os DataFrames finais para cada tabela de destino (
df_catalogo_insumos,df_precos_mensal, etc.).
- Enriquecimento de Contexto (Regime): Adicionar uma coluna
-
Carga (Ordem Crítica):
- Carregar Catálogos (UPSERT):
- Carregar
df_catalogo_insumosna tabelainsumosedf_catalogo_composicoesemcomposicoes. - Lógica: Usar
ON CONFLICT (codigo) DO UPDATE SET descricao = EXCLUDED.descricao, .... - Importante: Não atualizar a coluna
statusnesta etapa. Novos itens serão inseridos com ostatusdefault ('ATIVO').
- Carregar
- Recarregar Estrutura (TRUNCATE/INSERT):
- Executar
TRUNCATE TABLE composicao_insumos, composicao_subcomposicoes;. - Inserir os novos DataFrames de estrutura. .
- Executar
- Carregar Dados Mensais (INSERT):
- Inserir os DataFrames de preços e custos em suas respectivas tabelas. Utilizar
ON CONFLICT DO NOTHINGpara segurança em re-execuções.
- Inserir os DataFrames de preços e custos em suas respectivas tabelas. Utilizar
- Carregar Catálogos (UPSERT):
O modelo de dados robusto criado pelo autoSINAPI serve como uma base poderosa tanto para o uso programático (toolkit) quanto para a criação de APIs RESTful performáticas. Esta seção descreve a interface principal do toolkit e exemplifica endpoints que podem ser construídos sobre os dados processados.
A maneira recomendada de interagir com o pacote é através da função run_etl, localizada no nível raiz do pacote (from autosinapi import run_etl). Ela atua como uma interface de alto nível que simplifica a execução de todo o pipeline, gerenciando a configuração, a execução e o retorno de resultados de forma padronizada.
Existem duas formas principais de fornecer as configurações para a função run_etl:
- Via Dicionários Python: Ideal para integrar o
autoSINAPIem outras aplicações Python, como APIs, scripts de automação ou notebooks de análise. - Via Variáveis de Ambiente: Perfeito para ambientes automatizados, contêineres (Docker) e pipelines de CI/CD, onde as configurações são injetadas no ambiente de execução.
| Parâmetro | Tipo | Descrição | Padrão |
|---|---|---|---|
db_config |
Dict |
Dicionário com as credenciais de conexão do PostgreSQL. Se None, tentará carregar a partir de variáveis de ambiente (POSTGRES_*). |
None |
sinapi_config |
Dict |
Dicionário com as configurações de referência dos dados SINAPI. Se None, tentará carregar a partir de variáveis de ambiente (AUTOSINAPI_*). |
None |
mode |
str |
Modo de operação: 'local' (baixa os arquivos) ou 'server' (usa arquivos locais, útil em ambientes onde o download é feito por outro processo). |
'local' |
log_level |
str |
Nível de detalhe dos logs. Opções: 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'. |
'INFO' |
1. Dicionário db_config
Todos os campos são obrigatórios ao usar este método.
{
# Endereço do servidor de banco de dados.
# Ex: "localhost" para uma máquina local ou "db" em um ambiente Docker Compose.
"host": "seu_host_db",
# Porta em que o PostgreSQL está escutando. A padrão é 5432.
"port": 5432,
# O nome do banco de dados que será utilizado pelo pipeline.
"database": "seu_db_name",
# Nome de usuário com permissões para criar tabelas e inserir dados.
"user": "seu_usuario",
# Senha correspondente ao usuário.
"password": "sua_senha"
}2. Dicionário sinapi_config
year e month são obrigatórios. Os demais possuem valores padrão.
{
# Ano de referência dos dados do SINAPI a serem processados.
"year": 2025,
# Mês de referência (número inteiro de 1 a 12).
"month": 7,
# Tipo de caderno SINAPI. Padrão: "REFERENCIA".
# Opções: "REFERENCIA", "DESONERADO".
"type": "REFERENCIA",
# Política para lidar com dados de um período já existente. (ainda não implementado)
# Padrão: "substituir". Opções: "substituir", "append".
"duplicate_policy": "substituir"
}Exemplo 1: Execução programática via Dicionários
Este é o método ideal para usar o autoSINAPI como uma biblioteca dentro de outra aplicação Python.
from autosinapi import run_etl
# 1. Defina as configurações do banco de dados
db_settings = {
"host": "localhost",
"port": 5432,
"database": "sinapi_db",
"user": "postgres",
"password": "mysecretpassword"
}
# 2. Defina as configurações do SINAPI para o período desejado
sinapi_settings = {
"year": 2025,
"month": 7
}
# 3. Execute o pipeline e capture o resultado
print("Iniciando o pipeline ETL do SINAPI...")
result = run_etl(
db_config=db_settings,
sinapi_config=sinapi_settings,
log_level='DEBUG' # Use DEBUG para ver logs mais detalhados
)
# 4. Verifique o resultado da execução
print("\n--- Resultado da Execução ---")
print(f"Status: {result['status']}")
print(f"Mensagem: {result['message']}")
print(f"Registros Inseridos: {result['records_inserted']}")
print(f"Tabelas Atualizadas: {result['tables_updated']}")Exemplo 2: Execução via Variáveis de Ambiente
Este método é ideal para scripts de automação e ambientes de contêiner. Primeiro, configure as variáveis de ambiente no seu terminal.
No Linux ou macOS:
export POSTGRES_HOST=localhost
export POSTGRES_PORT=5432
export POSTGRES_DB=sinapi_db
export POSTGRES_USER=postgres
export POSTGRES_PASSWORD=mysecretpassword
export AUTOSINAPI_YEAR=2025
export AUTOSINAPI_MONTH=7No Windows (Prompt de Comando):
set POSTGRES_HOST=localhost
set POSTGRES_DB=sinapi_db
... (e assim por diante)Em seguida, o script Python para executar o pipeline se torna extremamente simples:
from autosinapi import run_etl
# A função run_etl irá carregar todas as configurações
# automaticamente a partir das variáveis de ambiente definidas.
print("Iniciando o pipeline ETL do SINAPI a partir de variáveis de ambiente...")
result = run_etl()
# O resultado é tratado da mesma forma
print("\n--- Resultado da Execução ---")
print(f"Status: {result['status']}")
# ... etc ...A função run_etl sempre retorna um dicionário com a seguinte estrutura, permitindo que a aplicação que a chamou saiba exatamente o que aconteceu.
| Chave | Tipo | Descrição |
|---|---|---|
status |
str |
O status final da execução. Ex: "SUCESSO", "FALHA", "SUCESSO (SEM DADOS)". |
message |
str |
Uma mensagem descritiva sobre o resultado da execução. |
records_inserted |
int |
O número total de registros inseridos no banco de dados durante a execução. |
tables_updated |
List[str] |
Uma lista com os nomes de todas as tabelas que foram modificadas. |
A estrutura do banco de dados permite a criação de endpoints de API poderosos para consultar os dados de forma eficiente.
| Endpoint | GET /custo_composicao |
| Parâmetros | codigo, uf, data_referencia, regime |
| Lógica | Busca direta na tabela custos_composicoes_mensal, com um JOIN opcional na tabela composicoes para verificar o status do item (ativo/inativo). |
<br>
| Endpoint | GET /composicao/{codigo}/estrutura |
| Lógica | Utiliza a view vw_composicao_itens_unificados para montar a árvore completa de insumos e subcomposições de um item. Uma consulta recursiva (CTE) é ideal para esta finalidade. |
<br>
| Endpoint | GET /insumo/{codigo}/historico |
| Lógica | Consulta direta na tabela manutencoes_historico para retornar todas as manutenções (inclusão, exclusão, alteração) de um insumo específico, ordenadas por data. |
| Exemplo SQL | sql<br>SELECT * FROM manutencoes_historico<br>WHERE item_codigo = :codigo AND tipo_item = 'INSUMO'<br>ORDER BY data_referencia DESC;<br> |
A adoção desta arquitetura de dados e fluxo de ETL resultará em um sistema:
- Robusto: Capaz de lidar com a evolução dos dados do SINAPI ao longo do tempo.
- Rastreável: Mantém um histórico completo das alterações, fundamental para auditoria e análise comparativa.
- Performático: O modelo normalizado permite consultas rápidas e eficientes.
- Flexível: A estrutura suporta uma ampla gama de consultas, desde simples buscas de preço até análises complexas de planejamento.