
- Setor
- Energia
- Ferramentas
- Python, Apache Airflow, dbt, PostgreSQL, Power BI, Docker, GitHub Actions
- Entregas
- Carga incremental orquestrada pelo Airflow
- Modelo dimensional com 41 testes de dados
- Painel em Power BI publicado, com tour de documentação
- Integração contínua com lint, testes e dbt
Desafio
Quando falta transmissão para escoar a energia, quando a segurança da rede exige ou quando há mais oferta que consumo, o ONS (Operador Nacional do Sistema Elétrico) manda usinas eólicas e solares reduzirem a geração. O setor chama isso de constrained-off ou curtailment. Os cortes cresceram a partir de 2023 e se concentram no Nordeste.
O ONS publica esses registros como dados abertos: um arquivo Parquet por mês e por fonte, com cada usina a cada 30 minutos, a geração verificada, a geração que deixou de acontecer e a razão da restrição. O arquivo do mês corrente é atualizado duas vezes por dia, e os meses anteriores também são regravados. O de outubro de 2026, por exemplo, mudou em 5 de novembro. Para saber quanto foi cortado, onde e por qual motivo, o pipeline precisa perceber o que mudou, recarregar só essa parte e manter o histórico consistente.
Este projeto de estudo monta esse caminho com Airflow para a carga, dbt sobre Postgres para o modelo e os testes, GitHub Actions para a integração contínua e Power BI para o painel. Tudo sobe com Docker Compose.
Solução

- Carga. Cada fonte tem um DAG no Airflow que roda às 12h30 e às 19h30, logo depois das atualizações do ONS. Ele confere o mês corrente e os dois anteriores e compara o ETag de cada arquivo com o da última carga. Quando o arquivo mudou, baixa e troca as linhas daquele mês numa transação. Rodar a mesma carga duas vezes deixa o banco igual, e uma coluna nova no arquivo vira coluna nova na tabela antes da inserção.
- Modelo. O DAG do dbt não tem horário: ele roda quando uma das cargas termina, porque cada carga atualiza um Asset do Airflow. A staging converte os MW médios de cada meia hora em MWh e junta as duas fontes. Um snapshot guarda o histórico do cadastro das usinas (nome, agente operador e ponto de conexão).
- Marts. A fato tem uma linha por usina e dia, com a geração não realizada separada por razão e por origem da restrição. Ela é incremental em lotes mensais e refaz a mesma janela de três meses que a carga confere. Uma segunda fato guarda o perfil por hora do dia, e as dimensões de usina e de data completam o modelo.
- Painel. O Power BI importa a camada marts. O projeto fica no formato PBIP, com o modelo em TMDL e o relatório em PBIR, arquivos de texto que entram no Git e passam por revisão como o resto do código.
O adaptador do dbt para Postgres faz o lote incremental com merge pela chave. Se o ONS retirasse uma usina de um mês já publicado, ela continuaria na fato. Uma macro própria troca esse comportamento por apagar a janela do lote e inserir de novo.
Os 41 testes de dados rodam a cada execução, junto com um teste unitário da staging. Além das chaves, eles conferem os códigos de razão e de origem, que nenhuma geração não realizada é negativa, que as razões somam o total de cada dia e que a soma mensal da fato bate com a da staging. No GitHub Actions, cada push passa pelo lint do SQL, pelos testes dos DAGs e por um dbt build sobre uma amostra de três dias e 15 usinas, rodado duas vezes para testar também o caminho incremental.
No cadastro do ONS, 14 nomes valem para 32 usinas. Metade são parques híbridos, com uma eólica e uma solar de mesmo nome; os outros são homônimos da mesma fonte. Agrupado pelo nome, o painel somaria usinas diferentes no ranking e na ficha. A dimensão de usina ganhou um rótulo que acrescenta a fonte e, quando o nome ainda se repete, o código da usina. Um teste do dbt garante que o rótulo é único.

O painel tem cinco páginas. A Visão executiva traz os indicadores dos últimos 12 meses com a variação contra os 12 anteriores, o corte mês a mês por fonte e destaques escritos por medidas DAX. As outras páginas tratam das razões e da sazonalidade, da rede e do território, das usinas e dos agentes e do detalhe de uma usina escolhida. Os filtros ficam numa gaveta que vale para todas as páginas, os gráficos filtram a página ao clique, e a dica de ferramenta de um mês, de um estado ou de uma usina mostra o % cortado, a comparação com o ano anterior e as razões do corte. Cada página tem um botão Documentação, que percorre os visuais um a um e explica o que mostram, como ler e como cada medida é calculada.
Resultado
- 87.955 GWh deixaram de ser gerados entre outubro de 2021 e 5 de novembro de 2026. Só em 2025 foram 37.217 GWh, quase o triplo de 2024.
- Em 2025, 20,7% da geração possível das usinas foi cortada: 25,7% nas solares e 19,1% nas eólicas.
- O subsistema Nordeste concentra 85% da energia cortada. Rio Grande do Norte e Bahia, juntos, respondem por 61%.
- No histórico, 56% do corte teve razão energética (mais oferta que consumo), 32% veio de confiabilidade da rede e 12% de indisponibilidade de equipamentos externos à usina. Nos últimos 12 meses, a razão energética subiu para 70%. Quatro de cada cinco GWh cortados vieram de restrições do sistema, fora da rede local da usina.
- A carga roda sozinha duas vezes por dia e só baixa os meses que o ONS mudou. Os modelos e os testes rodam logo depois de cada carga.
O botão Ver projeto abre o painel publicado. O código está no GitHub, com a licença MIT, as instruções para subir o ambiente com Docker e os arquivos do painel.
Projeto de estudo da Dome Analytics com dados abertos do ONS, sob a licença CC BY 4.0. Não tem relação com o ONS nem com as usinas e os agentes citados.
Projetos relacionados
Internações do SUS em Pernambuco, dos Arquivos do DATASUS a um Painel
Pipeline com Spark e dbt que leva os arquivos públicos de internações do SUS a um modelo testado e a um painel com as 666 mil internações de 2024 em Pernambuco.
Causa Raiz de Quedas no Gateway do Power BI
Investigação com os logs do Windows e do gateway que explicou por que o serviço caía em horário comercial e derrubava a atualização de dezenas de painéis.