Dados abertos que mudam depois de publicados: como manter a carga em dia

Dados abertos que mudam depois de publicados: como manter a carga em dia

Muitos órgãos publicam dados abertos em arquivos mensais e corrigem esses arquivos depois. O ONS (Operador Nacional do Sistema Elétrico), por exemplo, atualiza o mês corrente duas vezes por dia e regrava meses anteriores: o arquivo de outubro de 2026 dos cortes de geração eólica mudou em 5 de novembro. Uma carga que só baixa o mês novo perde essas correções. Baixar tudo de novo todo dia resolve, mas fica lento quando o histórico passa de 16 milhões de linhas. Este roteiro mostra como recarregar só o que mudou e manter o modelo coerente com a fonte.

1. Descubra até onde a fonte volta

Antes de escrever a carga, observe a fonte por algumas semanas. Guarde a data de modificação de cada arquivo e veja quais meses mudam e com que atraso. No caso do ONS, as mudanças aparecem no mês corrente e nos dois anteriores. Essa janela de três meses passa a ser o que cada execução confere.

Uma janela curta demais deixa correções de fora. Uma janela longa demais só custa tempo de execução, então na dúvida vale começar folgado e encurtar depois de ver o histórico.

2. Use o ETag para saber se o arquivo mudou

Arquivos publicados em buckets como o S3 respondem a uma requisição HEAD com o cabeçalho ETag, que muda quando o conteúdo muda. A carga guarda o ETag de cada arquivo numa tabela de controle e compara antes de baixar:

def cabecalho(url):
    try:
        with urllib.request.urlopen(urllib.request.Request(url, method="HEAD"), timeout=30) as resp:
            return {"etag": resp.headers["ETag"].strip('"'),
                    "bytes": int(resp.headers["Content-Length"])}
    except urllib.error.HTTPError as erro:
        if erro.code in (403, 404):  # mês ainda não publicado
            return None
        raise

Se o ETag for igual ao da última carga, o arquivo fica de fora. Na maior parte das execuções, a carga faz três requisições HEAD por fonte e não baixa nada.

A tabela de controle também ajuda no dia a dia. Com o mês, o ETag, o tamanho, o número de linhas e a hora da carga, dá para responder quando um mês mudou pela última vez sem abrir log nenhum.

3. Troque o mês inteiro numa transação

Quando o arquivo mudou, a carga apaga as linhas daquele arquivo, insere as novas e atualiza a tabela de controle, tudo na mesma transação:

begin;
delete from raw.restricao_eolica where _arquivo = 'RESTRICAO_COFF_EOLICA_2026_10.parquet';
copy raw.restricao_eolica (...) from stdin with (format csv);
insert into raw._cargas (...) values (...)
  on conflict (fonte, arquivo) do update set etag = excluded.etag, linhas = excluded.linhas;
commit;

Trocar o mês inteiro trata um caso que o upsert por chave deixa passar: a linha que a fonte retirou na revisão. Com upsert, ela continuaria no banco. Por causa da transação, quem consulta durante a carga continua vendo o mês antigo até o commit. E rodar a mesma carga duas vezes deixa o banco igual.

Vale também prever colunas novas. Se o arquivo trouxer uma coluna que a tabela não tem, a carga cria a coluna antes de inserir, e a execução segue.

4. Leve a mesma janela para o modelo

Se a carga refaz três meses e o modelo recalcula só o último, as correções param no banco e não chegam ao painel. No dbt, a estratégia microbatch processa a tabela em lotes por período, e o lookback diz quantos lotes anteriores refazer:

{{ config(
    materialized='incremental',
    incremental_strategy='microbatch',
    event_time='dia',
    begin='2021-10-01',
    batch_size='month',
    lookback=2
) }}

No Postgres, o adaptador do dbt grava cada lote com merge pela chave, e o problema do passo anterior volta: uma usina que o ONS retirasse de um mês revisado continuaria na fato. A solução foi uma macro que sobrescreve postgres__get_incremental_microbatch_sql e, para cada lote, apaga o intervalo de datas inteiro antes de inserir. É o mesmo comportamento que os adaptadores de Snowflake e BigQuery já têm.

5. Teste que os totais batem

No projeto, três testes do dbt vigiam essas recargas:

  • chave única por usina e dia na fato, para pegar linha duplicada depois de uma recarga;
  • soma mensal da fato igual à da staging, para pegar mês que ficou pela metade;
  • valores aceitos nos códigos de razão e de origem, para perceber quando a fonte inventa um código novo.

No GitHub Actions, o projeto carrega uma amostra de três dias num Postgres descartável e roda o dbt build duas vezes. A segunda execução já é incremental, e é ela que mostra se o lote refeito deixa os totais iguais.

6. Dispare o modelo quando a carga muda

Com Airflow 3, cada DAG de carga atualiza um Asset que representa a sua tabela no banco, e o DAG do dbt não tem horário: ele roda quando uma das cargas termina. Antes do dbt build, um dbt source freshness confere se as fontes estão em dia. Assim, o modelo roda logo depois de cada carga conferida, sem horário fixo para acertar, e um atraso da fonte aparece como falha no Airflow.

Usamos esse caminho no projeto Cortes de Geração Eólica e Solar, que acompanha a energia que as usinas deixaram de gerar por ordem do ONS desde 2021. O painel em Power BI mostra o resultado, e o código está no GitHub.

Sua carga perde as correções da fonte? Converse com a Dome.

« Voltar ao blog