Pipeline de dados climáticos em Medallion Architecture (Bronze → Silver → Gold), com ingestão via API pública, orquestração manual e exportação (reverse ETL) para um banco Postgres externo com gerenciamento de segredos via Infisical.
- Fonte de dados: Open-Meteo Archive API — pública, sem autenticação
- Plataforma: Databricks Free Edition (compute serverless)
- Linguagem: Python (PySpark) + SQL
- Cidades monitoradas: 12 (9 brasileiras + Lisboa, Buenos Aires, Cidade do México)
Open-Meteo Archive API
│ (1 chamada por cidade, incremental por watermark)
▼
┌───────────────────┐
│ BRONZE │ bronze_clima.clima_raw
│ clima_raw │ particionado por cidade · append
└───────────────────┘
│
▼
┌───────────────────┐
│ SILVER │ silver_clima.dim_cidade (SCD Tipo 1)
│ dim_cidade │ silver_clima.fato_clima_diario (+ condicao_climatica,
│ fato_clima_diario │ média móvel 7d) · particionado por cidade
└───────────────────┘
│
▼
┌───────────────────┐
│ GOLD │ gold_clima.medias_mensais
│ medias_mensais │ gold_clima.ranking_chuva
│ ranking_chuva │ gold_clima.anomalias_climaticas
│ anomalias_climaticas │
└───────────────────┘
│
▼ (Data Serving / reverse ETL)
┌───────────────────┐ ┌─────────────┐
│ Infisical │◄──────│ Databricks │
│ (guarda a URL JDBC │ │ Secrets │
│ do Postgres) │ │ (scope "clima")│
└───────────────────┘ └─────────────┘
│
▼
┌───────────────────┐
│ Postgres (Neon) │ tabela medias_mensais
└───────────────────┘
Orquestração ponta a ponta via nb_05_master.py (execução sequencial com %run,
já que o Free Edition não tem aba de Jobs para agendamento).
| Notebook | Função |
|---|---|
nb_00_config.py |
Schemas, lista de cidades (lat/lon), schema Spark, helpers de log |
nb_01_bronze.py |
Ingestão incremental por cidade (watermark + retry/timeout) |
nb_02_silver.py |
SCD1 em dim_cidade + fato_clima_diario limpo e enriquecido |
nb_03_gold.py |
Médias mensais, ranking de chuva, anomalias climáticas |
nb_04_data_serving.py |
Exporta a Gold para o Postgres externo via Infisical |
nb_05_master.py |
Orquestra tudo em sequência |
- Conta no Databricks Free Edition
- Databricks CLI instalado localmente
- Conta no Neon (Postgres gratuito) e no Infisical (cofre de segredos)
databricks auth login --host <url-do-seu-workspace> --profile clima
databricks workspace import-dir ./notebooks /Users/<seu-email>/projeto_02_clima/notebooks --overwrite -p climaO Free Edition serverless não expõe variáveis de ambiente por notebook, então as credenciais da Machine Identity do Infisical ficam no cofre nativo do Databricks:
databricks secrets create-scope clima -p clima
databricks secrets put-secret clima INFISICAL_CLIENT_ID -p clima
databricks secrets put-secret clima INFISICAL_CLIENT_SECRET -p clima
databricks secrets put-secret clima INFISICAL_PROJECT_ID -p clima(cada comando pede o valor interativamente — nada fica no histórico do terminal)
No Infisical, crie um secret POSTGRES_JDBC_URL no ambiente dev, no formato:
jdbc:postgresql://HOST/BANCO?user=USUARIO&password=SENHA&sslmode=require
Abra nb_05_master no workspace do Databricks (compute serverless) e clique em
Run All, ou dispare via Jobs API:
databricks jobs submit --json '{"run_name":"clima","tasks":[{"task_key":"master","notebook_task":{"notebook_path":"/Users/<seu-email>/projeto_02_clima/notebooks/nb_05_master"}}]}' -p climaO Free Edition atual roda em compute serverless (Spark Connect), diferente do Community Edition clássico com cluster. Isso mudou algumas decisões em relação à documentação original do projeto:
- Sem cluster clássico → sem aba "Libraries" para instalar Maven/PyPI.
O driver JDBC do Postgres já vem disponível nativamente; o
infisicalsdké instalado via%pip installdentro do próprio notebook (não precisa restart). - Sem variáveis de ambiente por notebook → as credenciais da Machine Identity
do Infisical ficam no Databricks Secrets nativo (
dbutils.secrets), não em env vars como a documentação original sugeria. - Escrita em Postgres no serverless exige o conector nomeado
.format("postgresql")com opções separadas (host,port,database,user,password) — o formato genérico.format("jdbc")com umaurlsó é bloqueado por política de DML do serverless (SERVERLESS_WRITE_OPTIONS_NOT_ALLOWED). - Campo
relativehumidity_2m_meanda Archive API foi descontinuado — o nome atual érelative_humidity_2m_mean. Sem essa correção a ingestão falhava silenciosamente para todas as cidades (a Bronze nunca era criada, quebrando Silver/Gold em cascata).
- Bronze:
clima_rawparticionado porcidade, ~32 linhas por cidade e crescendo a cada execução - Silver:
dim_cidadecom 12 registros (SCD1),fato_clima_diariosem nulos emdata - Gold:
medias_mensais(24 linhas),ranking_chuva(Recife #1, 236,7mm),anomalias_climaticas(5 dias atípicos) - Data Serving: linhas confirmadas na tabela
medias_mensaisdo Postgres externo (Neon)
Simulação: coordenada do Recife alterada de -8.05 para -8.06 no CIDADES,
histórico da Bronze apagado e o pipeline reexecutado.
| Antes | Depois | |
|---|---|---|
dim_cidade.latitude (Recife) |
-8.05 | -8.06 |
Linhas em dim_cidade para Recife |
1 | 1 (sem histórico — comportamento SCD1 esperado) |
Coordenada revertida para o valor correto (-8.05) ao final do teste.
Simulação: apagados os últimos 3 dias de Salvador na Bronze, pipeline
reexecutado sem alterar as outras cidades.
| Cidade | Antes | Depois | Observação |
|---|---|---|---|
| Salvador | 28 linhas | 32 linhas | recuperou os 3 dias apagados + 1 dia novo disponível na API |
| Demais 10 cidades | 31 linhas | 32 linhas | +1 dia novo, watermark avançou sozinho |
| Recife | — | 31 linhas | reingestão completa após o teste de SCD1 |
Nenhuma cidade duplicou dados nem reprocessou dias já existentes — cada uma avançou exatamente o necessário, de forma independente.
- Bronze:
clima_rawparticionado por cidade, com retry/timeout e carga incremental por watermark - Silver:
dim_cidadecom SCD1 testado (ver acima),fato_clima_diarioparticionado - Gold:
medias_mensais,ranking_chuva,anomalias_climaticas,estacao_do_ano - Desafio
condicao_climaticae média móvel 7d implementados - Exportação JDBC/reverse ETL: dados gravados e confirmados no banco externo
- Orquestração:
nb_05_mastercriado e executado com sucesso de ponta a ponta - Carga incremental testada (crescimento por execução, sem duplicação)