Resiliência e Fallbacks¶
O agrobr foi projetado para ser robusto e resiliente a falhas. Este documento explica as camadas de defesa implementadas.
Camadas de Defesa¶
┌─────────────────────────────────────────────────────────────────┐
│ CAMADAS DE DEFESA - AGROBR │
├─────────────────────────────────────────────────────────────────┤
│ │
│ CAMADA 1: PREVENÇÃO │
│ ├─ Structure Monitor (6h) → Detecta mudanças antecipadas │
│ ├─ Golden Data Tests (CI) → Garante parsing não regride │
│ └─ Fingerprint Baseline → Referência do health --deep │
│ │
│ CAMADA 2: DETECÇÃO │
│ ├─ Coluna de valor em R$ → Parser recusa a página sem ela │
│ ├─ Fingerprint → Só no health --deep e no CI │
│ ├─ can_parse() Confidence → Parser reconhece estrutura? │
│ └─ User-Agent Rotation → Evita bloqueio de IP │
│ │
│ CAMADA 3: VALIDAÇÃO │
│ ├─ Pydantic Validation → Tipos e formatos corretos? │
│ └─ Sanity Check (opcional) → validate_sanity=True │
│ │
│ CAMADA 4: FALLBACK │
│ ├─ Parser Cascade → Tenta próximo parser │
│ ├─ Cache Fallback → Retorna cache stale │
│ └─ Source Fallback → Fonte alternativa (NA) │
│ │
│ CAMADA 5: ALERTAS │
│ ├─ Multi-canal → Slack, Discord, Email │
│ ├─ GitHub Issue → Tracking automático │
│ └─ Logging Estruturado → Debug facilitado │
│ │
└─────────────────────────────────────────────────────────────────┘
Retry com Exponential Backoff¶
Todas as requisições HTTP usam retry automático:
# Configuração padrão
max_retries = 3 # total de tentativas por pedido, contando a primeira
base_delay = 1.0 # segundos
max_delay = 30.0 # segundos
exponential_base = 2
# 3 tentativas, com esperas de 1s → 2s (máx 30s)
max_retries conta o total de tentativas, e não só as novas: o padrão 3 faz até 3 pedidos, com 2 esperas.
AGROBR_HTTP_MAX_RETRIES=0 (ou 1) faz um pedido só, sem retry; valor negativo é recusado na validação, com
pydantic.ValidationError (variáveis de ambiente). No retry_async e no with_retry, max_attempts=0
também vale uma tentativa, base_delay=0 e max_delay=0 valem espera zero, e valor negativo levanta
InvalidParameterError. Esgotadas as tentativas no retry das fontes (agrobr.http.retry.retry_on_status), sai
SourceUnavailableError, e a mensagem termina em after N attempts (after 1 attempt com uma tentativa), depois do erro
de rede (ReadTimeout: …) ou do status (HTTP 503). O retry_async e o with_retry relançam a última exceção original,
sem essa frase.
Status codes que acionam retry: - 408 Request Timeout - 429 Too Many Requests - 500 Internal Server Error - 502 Bad Gateway - 503 Service Unavailable - 504 Gateway Timeout
Além do status HTTP e do tamanho mínimo, as respostas são validadas antes de
chegar aos parsers. Clientes JSON convertem HTML de manutenção, bloqueios de WAF
e corpos vazios em SourceUnavailableError. Downloads conferem os magic bytes
de ZIP/XLSX, XLS e PDF; arquivos CSV rejeitam conteúdo que começa como HTML.
Assim, uma indisponibilidade da fonte não é confundida com quebra de layout.
Rate Limiting¶
Cada fonte tem seu próprio rate limit, configurável via env vars:
| Fonte | Intervalo | Env var |
|---|---|---|
| ABIOVE | 3 segundos | AGROBR_HTTP_RATE_LIMIT_ABIOVE |
| ANDA | 3 segundos | AGROBR_HTTP_RATE_LIMIT_ANDA |
| BCB | 1 segundo | AGROBR_HTTP_RATE_LIMIT_BCB |
| CEPEA | 5 segundos | AGROBR_HTTP_RATE_LIMIT_CEPEA |
| ComexStat | 2 segundos | AGROBR_HTTP_RATE_LIMIT_COMEXSTAT |
| CONAB | 3 segundos | AGROBR_HTTP_RATE_LIMIT_CONAB |
| DERAL | 3 segundos | AGROBR_HTTP_RATE_LIMIT_DERAL |
| IBGE | 1 segundo | AGROBR_HTTP_RATE_LIMIT_IBGE |
| IMEA | 1 segundo | AGROBR_HTTP_RATE_LIMIT_IMEA |
| INMET | 0.5 segundo | AGROBR_HTTP_RATE_LIMIT_INMET |
| NASA POWER | 1 segundo | AGROBR_HTTP_RATE_LIMIT_NASA_POWER |
| Notícias Agrícolas | 2 segundos | AGROBR_HTTP_RATE_LIMIT_NOTICIAS_AGRICOLAS |
| USDA | 1 segundo | AGROBR_HTTP_RATE_LIMIT_USDA |
| ZARC | 2 segundos | AGROBR_HTTP_RATE_LIMIT_ZARC |
| Default | 1 segundo | AGROBR_HTTP_RATE_LIMIT_DEFAULT |
A tabela mostra as principais fontes; cada uma das fontes suportadas tem seu próprio rate limit (default 1 segundo), inclusive a ANTAQ, que baixa pelo requests. Quatro pedidos internos não têm variável própria e usam o AGROBR_HTTP_RATE_LIMIT_DEFAULT: o custo de produção e a série histórica da CONAB, o Censo Agro legado do IBGE (FTP) e o MAPA PSR; o AGROBR_HTTP_RATE_LIMIT_CONAB e o AGROBR_HTTP_RATE_LIMIT_IBGE não valem para eles. Além do intervalo entre requisições, a concorrência por fonte é controlada por AGROBR_HTTP_MAX_CONCURRENT_<FONTE> só em quatro fontes: ANA (1), ANP Diesel (3), B3 (3) e IBGE (3); as demais usam AGROBR_HTTP_MAX_CONCURRENT_DEFAULT (1), e a variável de outra fonte (por exemplo, AGROBR_HTTP_MAX_CONCURRENT_CFTC) não tem efeito. Valor menor que 1 é recusado na validação, com pydantic.ValidationError. A concorrência vale via semáforos que permitem requests paralelos a fontes diferentes. O intervalo e a concorrência valem para o processo inteiro: entre chamadas do agrobr.sync (cada uma com o seu asyncio.run), entre loops e entre threads. A espera por vaga entre threads tem teto (AGROBR_HTTP_TIMEOUT_READ); no teto, o pedido segue com aviso, e o intervalo continua valendo.
Configuração HTTP Centralizada¶
Todos os clients usam HTTPSettings (env prefix AGROBR_HTTP_). A lista completa, com os padrões, está em
Variáveis de ambiente.
# Timeouts (segundos)
export AGROBR_HTTP_TIMEOUT_CONNECT=10
export AGROBR_HTTP_TIMEOUT_READ=30
export AGROBR_HTTP_TIMEOUT_WRITE=10
export AGROBR_HTTP_TIMEOUT_POOL=10
# Retry (MAX_RETRIES é o total de tentativas; 0 ou 1 = sem retry)
export AGROBR_HTTP_MAX_RETRIES=3
export AGROBR_HTTP_RETRY_BASE_DELAY=1.0
export AGROBR_HTTP_RETRY_MAX_DELAY=30.0
Os clientes das fontes fixam a leitura acima do padrão de 30 s: ComexStat 120 s; ZARC, PSR e SICAR 180 s;
os ZIPs históricos do INMET 600 s (a API observacional e o catálogo do INMET ficam no padrão de 30 s); as demais, entre
30 e 300 s, no get_timeout(read=...) de cada client.py.
AGROBR_HTTP_TIMEOUT_READ vale como mínimo: maior que o do cliente, prevalece; menor, fica o do cliente.
CONNECT, WRITE e POOL valem em todos os clientes httpx das fontes; na ANTAQ, que usa o requests, só valem
CONNECT e READ.
Via código:
from agrobr.http import get_timeout
timeout = get_timeout() # httpx.Timeout (defaults)
timeout = get_timeout(read=60.0) # leitura de 60 s, ou a do AGROBR_HTTP_TIMEOUT_READ, se maior
User-Agent Rotativo¶
Pool de User-Agents reais e atuais:
- Chrome Windows (múltiplas versões)
- Chrome Mac
- Firefox Windows/Mac
- Edge
- Safari
Rotação determinística por fonte para parecer tráfego natural.
Fallback de Encoding¶
Chain de fallback para encoding:
- UTF-8 (padrão)
- Windows-1252 (CP1252, padrão Excel BR — superset do Latin-1, vem antes porque ISO-8859-1 decodifica qualquer byte e mataria o resto da chain)
- ISO-8859-1 (Latin-1, comum em sites BR antigos). Decodifica qualquer sequência de bytes, então a chain termina nele: nenhum passo depois dele rodaria.
Fallback de Engine Excel¶
Planilhas XLSX de fontes governamentais podem conter estilos/fills malformados que crasham o openpyxl (bug conhecido desde 2021, sem fix upstream).
O agrobr usa fallback automático para python-calamine (engine Rust, MIT):
openpyxl (estilos + dados)
↓ falhou (stylesheet malformado)?
calamine (ignora estilos, extrai só dados)
↓ falhou?
ParseError
Arquivos OLE2/BIFF (.xls) passam pelo mesmo fallback: o pandas lê com o xlrd, e o calamine entra se ele falhar. A série do CEPEA faz o inverso: lê com o calamine e recorre ao xlrd com ignore_workbook_corruption.
Helpers: open_excel_safe() (multi-sheet) e read_excel_safe() (single-sheet)
em agrobr/utils/io.py.
Teto de expansão (ZIP e XLSX)¶
Um arquivo compactado pequeno pode expandir para gigabytes. Antes de descomprimir, o agrobr confere quanto cada membro de ZIP
e cada XLSX expande, contra um teto por fonte (constants.MAX_EXPANDED_BYTES), e levanta ResourceLimitError se passar:
- ZIP (Queimadas, B3, MapBiomas, ANTAQ e o Censo legado do IBGE): o membro passa por
read_zip_memberouopen_zip_member, que conferem o tamanho declarado. Ozipfilenão entrega mais do que o declarado: um membro que expande além dele falha no CRC. O Censo legado lê todas as tabelas do ZIP, e o teto vale também para a soma delas (read_zip_members). - XLSX (
read_excel_safe,open_excel_safe, a série do CEPEA, o MapBiomas municipal e a UNICA):check_xlsx_expansionsoma o tamanho declarado dos membros e depois mede a expansão real de cada um, descomprimindo em stream até passar do teto, antes do leitor de planilha. O calamine não respeita o tamanho declarado, e o CRC é escolhido por quem gera o arquivo: nenhum dos dois prova quanto o XLSX expande. ZIP ilegível (inclusive nome de membro marcado como UTF-8 que não é UTF-8), ou com membro comprimido por outro método que não o deflate, é recusado. - Planilha esparsa (os mesmos leitores de XLSX): o calamine e o openpyxl montam a planilha densa, de A1 até a maior
linha e a maior coluna, e poucas células espalhadas bastam para esgotar a memória e derrubar o processo. Depois do teto
de bytes,
check_xlsx_expansionmede esse retângulo em cada planilha (células, linhas e o<dimension>declarado), em stream e sem abrir o leitor, e recusa acima deconstants.MAX_XLSX_CELLS: 10 milhões de células, para todas as fontes. A maior planilha medida, a municipal do MapBiomas, tem 4,3 milhões. Planilha que não é XML bem formado, ou que traz DTD, também é recusada, porque o tamanho dela não é conhecido; pelo mesmo motivo, arquivo ZIP que não é XLSX (ODS, XLSB) também é recusado. - Shapefile do Acervo Fundiário (SIGEF, SNCI e assentamentos do INCRA): o GDAL também lê o membro até o fim do deflate,
sem parar no tamanho declarado. Antes de o pyogrio ou o GeoPandas abrir o ZIP,
check_zip_expansionfaz a mesma conta do XLSX, com o teto do download (ACERVO_MAX_DOWNLOAD_BYTES): o INCRA publica o ZIP sem compressão, e a expansão é o tamanho do próprio arquivo. Depois do teto, só passam as partes de um shapefile (.shp,.shx,.dbf,.prj,.cpg), e o.shpprecisa do cabeçalho do formato: o GDAL escolhe o leitor pelo conteúdo, não pela extensão, e o ZIP é aberto pelo caminho explícito do.shp. Outro conteúdo levantaParseErrorantes do GDAL.
| Fonte | Teto | Maior arquivo publicado (em 27/09/2026) |
|---|---|---|
| Queimadas | 2 GiB | CSV anual de 2024: 905 MB |
| B3 | 512 MiB | ZIP interno de 13 MB; XML de 144 MB |
| MapBiomas | 1 GiB | XLSX municipal: 267 MB |
| ANTAQ | 4 GiB | não medido: o site está fora do ar desde 23/06/2026 |
| IBGE (Censo legado, FTP), por tabela e pela soma | 16 MiB | 122 KB |
| Acervo Fundiário (INCRA) | 4 GiB | SIGEF privado de MG: 749 MB |
| ANP | 512 MiB | planilha de preços 2022–2023: 152 MB |
| ABIOVE, UNICA, CONAB e progresso da CONAB | 64 MiB | 0,8 MB, 1,0 MB, 3,9 MB e 0,2 MB |
| Demais fontes | 256 MiB | a série do CEPEA, a DERAL e a série histórica da CONAB publicam XLS, que não é compactado |
O custo de produção da CONAB e o histórico do INMET têm teto próprio (CONAB_CUSTOS_MAX_EXPANDED_BYTES e
INMET_HISTORICO_MAX_*). PDF não tem teto: o pdfplumber descomprime cada stream inteiro.
O WKT das geometrias do IBAMA (embargos_geo) e do MapBiomas Alerta (alertas_geo) passa por utils.geo.wkt_within_limits
antes do shapely: o leitor de WKT do GEOS é recursivo, e um WKT aninhado demais estoura a pilha e derruba o processo, sem
exceção. Com mais de 16 níveis de parênteses ou mais de 16.777.216 caracteres (constants.MAX_WKT_DEPTH e MAX_WKT_CHARS;
o maior WKT publicado tem 3 níveis e 1,5 MB), o valor é tratado como WKT inválido: no IBAMA, a linha sai com o aviso
ibama_embargos_geo_wkt_invalido; no MapBiomas Alerta, o alerta fica com geometria nula e aviso.
Pedido fora do host da fonte¶
conab.progresso_safra(semana_url=...) só segue páginas em https://www.gov.br/conab/. Outra URL, ou um redirecionamento ou
link que saia dela, levanta InvalidParameterError antes de o pedido sair. Numa aplicação que repassa a URL do usuário, isso
fecha o pedido a endereço interno.
A URL lida da resposta da fonte também só é seguida com https, no host da fonte, na porta padrão e sem credencial: o link do
PDF na página de recursos da ANDA (anda.org.br), o PDF do boletim no JSON do artigo da ANEC (www.anec.com.br) e o CSV da
safra no catálogo CKAN do ZARC (dados.agricultura.gov.br). A regra vale também para cada redirecionamento do download.
Fora disso, SourceUnavailableError antes do pedido: uma página
ou um catálogo adulterado não faz o agrobr pedir endereço interno da rede do usuário nem baixar por http://.
Fallback de Fonte¶
Quando a fonte primária de um dataset falha e uma fonte seguinte responde, o
agrobr emite SourceFallbackWarning com a fonte primária e, para cada fonte que
falhou na cascata, seu nome, a categoria e o resumo do erro na ordem das tentativas,
além do fallback selecionado. O aviso usa warnings.warn, portanto pode
ser capturado ou filtrado pelas ferramentas padrão do Python e segue para stderr.
CEPEA¶
CEPEA (www.cepea.org.br)
↓ bloqueado (Cloudflare)?
Notícias Agrícolas (httpx direto, SSR)
↓ soft block (consent/challenge page)?
↓ falhou (HTTP error)?
Cache local (DuckDB)
O Notícias Agrícolas republica os mesmos indicadores CEPEA/ESALQ via HTML server-side rendered, sem necessidade de Playwright.
Cada etapa retorna um FetchResult(html, source) que identifica explicitamente a origem do HTML ("cepea", "browser" ou "noticias_agricolas"), evitando detecção frágil por markers no conteúdo.
Soft block detection: Alguns usuários recebem do NA uma página de consent/challenge (HTTP 200, ~10KB sem tabela) em vez da página de dados (~75KB com tabela). O client NA valida o conteúdo antes de retornar: se o HTML é < 20KB e não contém <table, levanta SourceUnavailableError, ativando o cache fallback.
Cache¶
O cache local usa DuckDB e guarda só os indicadores do CEPEA, com as linhas do fallback Notícias Agrícolas. Eles vencem na primeira virada das 18h BRT (21h UTC) em dia útil depois da coleta (smart TTL). As demais fontes não usam esse cache: IBGE, BCB, ComexStat e SICAR consultam a fonte a cada chamada, e as que têm cache próprio (INMET, ZARC, RNC e o catálogo de custos da CONAB, entre outras) o descrevem na página delas. Quando o fetch do CEPEA falha, o cache stale é retornado com StaleDataWarning. Sem rede e sem cache, o comportamento depende da camada: a fonte direta cepea.indicador() levanta SourceUnavailableError (até a 1.1.0, devolvia a tabela vazia); os datasets (datasets.*, que tentam fontes em cascata) levantam SourceUnavailableError quando todas as fontes se esgotam.
Fluxo de Cache¶
O fallback interno para cache também emite SourceFallbackWarning nos datasets;
convertê-lo em erro interrompe a consulta. Cache quente/offline não gera uma nova
tentativa de fallback. MetaInfo.selected_source="cache" identifica o caminho
local, enquanto data_sources preserva as fontes das linhas retornadas.
Falhas de migração são diferentes de indisponibilidade de abertura do cache:
CacheMigrationError interrompe o acesso, sem degradar silenciosamente para vazio.
A conexão fica aberta só durante cada operação, e outro processo (um 2º notebook,
um worker) usa o mesmo cache; o upsert grava tudo ou nada. Sem acesso ao arquivo
(pasta sem escrita, disco cheio, outro processo gravando), a operação segue sem
cache, e o agrobr avisa uma vez (UserWarning) com o caminho, o motivo e a dica
AGROBR_CACHE_DIR. Banco ilegível (leitura incompleta, checksum ou arquivo inválido) vai para o lado
como agrobr.duckdb.corrompido-<AAAAMMDDHHMM>, com aviso, e a consulta seguinte cria um banco
novo (o que o agrobr grava no disco).
Migrações pendentes preservam os originais em quarentena e só retiram linhas da
área ativa na mesma transação que registra a versão. Veja preservação e
recuperação.
Request
│
▼
Cache fresh? ──yes──→ Retorna cache
│no
▼
Fetch fonte
│
├─success──→ Atualiza cache
│
└─fail──→ Cache stale? ──yes──→ Retorna stale + warning
│no
▼
cepea.indicador() → SourceUnavailableError
datasets.* → SourceUnavailableError
Fingerprinting de Layout¶
Compara a estrutura da página do CEPEA com uma baseline. Roda só no agrobr health --deep e no
Structure Monitor do CI: a coleta (cepea.indicador, datasets.preco_diario) não compara
fingerprint. Na coleta, a defesa contra mudança de layout é o parser: sem a coluna de valor em R$
reconhecida pelo cabeçalho, ele levanta ParseError, e a consulta segue para a Notícias Agrícolas
(quando habilitada) e depois para o cache. Um cabeçalho em US$ nunca vira preço em reais.
Componentes da fingerprint: - Classes CSS das tabelas - IDs relevantes (preço, indicador, etc.) - Headers de tabelas - Contagem de elementos estruturais - Hash da hierarquia de tags
Thresholds do health --deep:
| Similaridade | Resultado do check |
|---|---|
| > 85% | ok |
| 70-85% | warning (drift) |
| < 70% | failed (layout mudou muito) |
A baseline do health --deep vai no pacote (agrobr/health/baselines/cepea_baseline.json, a página
da soja de 27/09/2026), e o check vale de qualquer pasta. Sem ela, ou quando a página veio da
Notícias Agrícolas, o check sai warning com o motivo, sem comparar.
Validação Estatística¶
Os 22 identificadores CEPEA têm regras de unidade e faixa de valor. Para ativar
essa conferência na API, use cepea.indicador(produto, validate_sanity=True).
Uma unidade incompatível gera unit_mismatch antes da comparação numérica;
não há conversão implícita de moeda, peso ou centavos.
from agrobr.validators.sanity import PRICE_RULES
regra = PRICE_RULES["soja"]
print(regra.expected_unit) # BRL/sc60kg
print(regra.min_value) # 30
print(regra.max_value) # 300
print(regra.max_daily_change_pct) # 15
| Produto | Unidade esperada | Faixa inclusiva | Variação temporal máxima |
|---|---|---|---|
soja, soja_parana |
BRL/sc60kg | 30–300 | 15% |
milho |
BRL/sc60kg | 15–150 | 15% |
cafe, cafe_arabica |
BRL/sc60kg | 200–3000 | 10% |
cafe_robusta |
BRL/sc60kg | 100–3000 | 10% |
bezerro |
BRL/cabeca | 800–8000 | 10% |
boi, boi_gordo |
BRL/@ | 100–500 | 10% |
trigo |
BRL/ton | 20 × 1000/60 a 150 × 1000/60 | 15% |
algodao |
cBRL/lb | 50 × 100 × 0,45359237/15 a 250 × 100 × 0,45359237/15 | 10% |
arroz |
BRL/sc50kg | 8–300 | sem limite |
acucar |
BRL/sc50kg | 8–400 | sem limite |
acucar_refinado |
BRL/kg | 0,2–8 | sem limite |
frango_congelado, frango_resfriado |
BRL/kg | 0,6–20 | sem limite |
suino |
BRL/kg | 0,8–30 | sem limite |
etanol_hidratado |
BRL/L | 0,1–8 | sem limite diário; série semanal |
etanol_anidro |
BRL/L | 0,1–10 | sem limite diário; série semanal |
leite |
BRL/L | 0,1–8 | sem limite diário; série mensal |
laranja_industria, laranja_in_natura |
BRL/cx40.8kg | 4–300 | sem limite |
As faixas são escolhas de engenharia e precisam ser revistas quando o mercado
muda. Para arroz, açúcares, frangos, suíno, etanóis e leite, os novos limites usam
metade do mínimo positivo e o dobro do máximo do histórico oficial disponível
em setembro de 2026, arredondados para fora a um algarismo significativo. Os
zeros publicados no histórico de leite não entram nessa calibração: o modelo
Indicador já exige preço positivo.
Citros usa referências pontuais oficiais de 2014/2015 e 2024; sua faixa não representa uma varredura histórica completa. As nove regras preexistentes não foram recalibradas. Soja Paraná herda a política de faixa da soja, conservando sua série regional; café arábica é um alias de café. Nenhum limite de variação foi inferido para os outros onze produtos novos.
A comparação temporal separa produto, praça e unidade; frangos, etanóis e
laranjas continuam séries distintas. As faixas não são intervalos de confiança
nem garantem detectar todo erro de escala. validate_sanity=True acrescenta
anomalias ao retorno sem bloqueá-lo, com um resumo das linhas marcadas (quantas e quais
regras) em MetaInfo.validation_warnings e UserWarning; o helper validate_batch(..., strict=True)
rejeita anomalias críticas. Regras customizadas podem omitir expected_unit para
preservar o comportamento anterior.
Health Checks¶
Verificações automáticas:
- Conectividade: HTTP GET responde?
- Latência: < 5 segundos?
- Parsing: Parser extrai dados?
- Fingerprint (CEPEA, só com
--deep): estrutura similar à baseline do pacote? Sem baseline, ou com a página vinda da Notícias Agrícolas, saiwarningcom o motivo.
Consultas HTTP que levam mais de 5 segundos são repetidas uma vez. O health usa
a menor latência e registra a primeira medição em cold_start_ms, evitando que o
cold start de serviços como o ArcGIS da ANA seja contado como falha. O Comtrade é
sondado pelo endpoint público guest, sem exigir chave de API.
O workflow persiste os contadores no DuckDB, fecha o store antes de salvar o cache e envia a quantidade de falhas anterior nos alertas de recuperação. Se o store não puder ser aberto por corrupção, lock ou permissão, o run informa a degradação e termina com erro em vez de zerar os contadores silenciosamente.
GitHub Actions¶
- Daily Health Check: 2x ao dia (9h e 21h BRT)
- Structure Monitor: A cada 6 horas
- Tests: Em cada PR
- Reconciliação semanal: segundas às 6h BRT (abaixo)
Reconciliação semanal¶
O workflow reconciliacao.yml roda os scripts/reconciliar_*.py contra as fontes oficiais, um
por vez, e dá a cada fonte um estado:
ok;mismatch;indisponível: a fonte está fora do ar ou bloqueou o runner;não verificado: falta a credencial, ou o script está fora da execução semanal (sem modo ao vivo, por exemplo). Nunca é sucesso;erro do script.
O resumo fica como artefato da execução. O job falha com mismatch ou erro do script.
Localmente, python -m scripts.reconciliacao_semanal imea roda uma fonte e grava em
reports/reconciliacao_semanal/ o resumo e o JSON completo do script.
Cada fonte com mismatch ganha a issue "Reconciliação semanal: mismatch em
Alertas¶
Canais Suportados¶
# Slack
export AGROBR_ALERT_SLACK_WEBHOOK=https://hooks.slack.com/...
# Discord
export AGROBR_ALERT_DISCORD_WEBHOOK=https://discord.com/api/webhooks/...
# Email (SendGrid); a lista vai em JSON, entre aspas simples no shell
export AGROBR_ALERT_SENDGRID_API_KEY=SG...
export AGROBR_ALERT_EMAIL_TO='["admin@example.com"]'
A URL do webhook do Slack e do Discord é a própria credencial. Com o log de INFO do httpx ligado pela aplicação, o
agrobr a troca por [REDACTED] na linha HTTP Request do envio do alerta. A mensagem de erro de resposta não JSON também
mascara as credenciais do agrobr, do ambiente ou passadas por argumento.
O envio ao Slack e ao Discord repete até 3 vezes em 429, 5xx ou erro de rede, com a espera do Retry-After limitada a
10 s (1 s sem o cabeçalho). Um 5xx depois de o servidor entregar a mensagem a duplica: duplicar é melhor que perder o
alerta de limiar, que não se repete na execução seguinte. A falha que sobra vai ao log alert_send_failed, com o canal
(slack, discord ou email).
Níveis de Alerta¶
| Nível | Trigger | Canais |
|---|---|---|
| Info | Fonte voltou (recuperação) | Todos os canais configurados |
| Warning | Falhas seguidas no limiar de aviso, soft block, drift de estrutura | Todos os canais configurados |
| Critical | Falhas seguidas no limiar crítico, erro ao baixar a estrutura no Structure Monitor | Todos os canais configurados |
Modo Offline¶
Para trabalhar sem conexão:
Usa apenas o cache local.
Comando Doctor¶
Use o comando doctor para diagnosticar saúde do sistema:
Exemplo de saída¶
Diagnóstico do agrobr v2.0.0
==================================================
Conexão com as fontes
[OK] CEPEA 142ms
[OK] CONAB 89ms
[OK] IBGE 67ms
Cache
Status: ok
Erro: -
Local: ~/.agrobr/cache/agrobr.duckdb
Tamanho: 2.40 MB
Registros: 1,152
Por fonte:
CEPEA: 847 registros (de 2025-01-21 a 2026-02-04)
NOTICIAS_AGRICOLAS: 305 registros (de 2024-01-01 a 2026-02-04)
Validade do cache
CEPEA: Expira às 18h BRT (atualização CEPEA)
Configuração
Navegador como alternativa: desligado
Fonte alternativa: ligada
[OK] Tudo funcionando
Output JSON¶
Para integração com sistemas de monitoramento:
Verbose¶
Para informações detalhadas: