Mantenha seus dados atualizados
Um painel só vale sua URL se os números nele estiverem atuais. Essa é a parte que uma sessão de agente não pode deixar para trás: seu agente constrói o painel e vai embora, e os dados por baixo precisam continuar chegando em um agendamento, por meses, sem que ninguém precise acompanhar.
A atualidade dos dados é, portanto, estado da plataforma, não código do painel. Você a configura uma vez contra um conjunto de dados; ela continua rodando quer alguém esteja com uma sessão aberta, quer não.
As três formas de os dados avançarem
| O que é | Quem dispara | |
|---|---|---|
| Atualização sob demanda | Rebuscar um conjunto de dados agora | Você, seu agente, ou seu CI — POST /v1/datasets/{dataset_id}/refresh |
| Carga incremental | Buscar apenas o que mudou desde a última execução | A mesma chamada, com mode: "incremental" |
| Atualização agendada | Uma expressão cron que a plataforma executa para você | Ninguém — o poller de cron da plataforma |
Atualize um conjunto de dados agora
result = client.datasets.refresh(dataset_id)
print(result.completed, result.effective_mode, result.message)
const result = await client.datasets.refresh(datasetId);
console.log(result.completed, result.effectiveMode, result.message);
Dois resultados possíveis, e a diferença importa:
- 200 OK — o trabalho realmente terminou dentro da requisição.
completedétrueedataseté a linha final. É isso que um novo download de um conjunto de dados público faz. - 202 Accepted — a atualização foi iniciada. A linha é travada e marcada como
PROCESSING, e um conector roda segundos depois.completedéfalse. Faça polling do conjunto de dados, ou escute no websocket, até queprocessingStatussaia dePROCESSING.
Um 202 não é uma falha e não é um sucesso — é uma promessa. Trate "a chamada retornou" e "os
dados estão atuais" como dois fatos diferentes, e verifique o segundo antes de dizer a alguém
que o painel está em dia. A chamada que verifica isso é
get_data_freshness — tudo o que vem antes dessa seção trata de
fazer os dados se moverem, não de quão antigos eles são.
Cargas incrementais, e a execução que ainda não é incremental
Passe um modo para buscar apenas linhas novas:
from vibedasher.models import RefreshRequest
result = client.datasets.refresh(dataset_id, RefreshRequest(mode="incremental"))
print(result.requested_mode, "->", result.effective_mode)
requestedMode ecoa o que você pediu. effectiveMode é o que de fato rodou, e eles nem
sempre são iguais. Uma requisição incremental contra um conjunto de dados que ainda não tem
checkpoint retorna effectiveMode: "bootstrap": ela roda como uma sobrescrita completa e
registra a marca d'água, então a próxima execução é a primeira genuinamente incremental.
Baseie sua lógica em effectiveMode, nunca no que você pediu. (Essa é a mesma regra de
client.query, que reporta a via de execução que de fato usou, em vez daquela que você pediu —
um padrão que vale a pena internalizar: a plataforma diz o que aconteceu, e é seu trabalho ler
isso.)
Para forçar a próxima execução a buscar tudo de novo, limpe a marca d'água:
client.datasets.reset_checkpoint(dataset_id)
Atualização agendada
Um agendamento é uma linha que você cria uma vez. Ela carrega uma expressão cron e o projeto dbt que atualiza:
from vibedasher.models import RefreshCreate
schedule = client.refreshes.create_refresh(
RefreshCreate(name="nightly", project_id=project_id, cron="0 3 * * *")
)
print(schedule.refresh.next_run, schedule.schedule_registration.registered)
A resposta carrega um bloco scheduleRegistration. Verifique registered. Ele é true
somente quando o agendamento está de fato ativo; quando é false, reason e message dizem o
porquê, e nextRun é nulo.
Esse campo existe por causa de uma interrupção real, e a história é o melhor argumento a favor
do design atual. A implementação original registrava cada agendamento como sua própria regra na
nuvem, envolvia a chamada em um except amplo, e respondia 201 Created com um nextRun
confiante. O papel da API não tinha permissão para criar essas regras, então nada jamais foi
registrado — e todo cliente era informado de que seus dados seriam atualizados. Isso foi
descoberto em 05-09-2026: zero regras existiam, e o único agendamento no banco de dados tinha um
nextRun cinco meses no passado e uma contagem de execuções igual a zero.
A correção não foi um tratamento de erros melhor. Foi remover a coisa que podia falhar: a linha é o agendamento. Um poller de cron roda a cada cinco minutos, reivindica cada linha vencida com um compare-and-set, e a executa. Não há registro externo para ter sucesso ou falhar, então a única coisa que pode dar errado no momento da criação é uma expressão cron que não pode ser avaliada — e isso agora é rejeitado logo de início, não no momento do disparo.
Duas consequências que vale a pena planejar:
- Um agendamento dispara dentro de cerca de cinco minutos do seu horário previsto, não no segundo exato. Cron aqui significa "não antes de", não "exatamente às".
scheduleRegistration.backendtrazdb_cron. Ele é mantido na resposta porque clientes baseiam lógica nele.
Lendo o que aconteceu
Cada linha de agendamento carrega seu próprio histórico — lastRun, lastStatus, lastError,
executionCount, nextRun:
for r in client.refreshes.iter_all():
print(r.name, r.last_status, r.last_run, r.execution_count, r.last_error)
executionCount é o campo a observar. Um agendamento que existe há uma semana com
executionCount: 0 não está lento — ele não está rodando, e é exatamente essa a falha que o
design acima foi construído para tornar visível em vez de silenciosa.
Qual é a idade destes números?
Tudo acima responde a «o pipeline está funcionando?». Não responde a «o que este painel mostra está atual?», e as duas coisas se separam nas duas direções:
- Um agendamento pode rodar perfeitamente e não carregar nada de novo.
executionCountsobe,lastStatusésuccess, e a linha mais recente ainda é de março. - Um conjunto de dados pode não ter agendamento algum, e então
executionCountnão tem linha para ler. Um upload estático é o caso comum: suas linhas mudam quando alguém sobe novas e em nenhum outro momento, então uma lista de agendamentos saudável pode conviver com um conjunto de dados de nove dias sem dizer nada sobre ele. A ausência de um agendamento não é um agendamento com contador ruim.
Uma única chamada responde à pergunta real do leitor, medida a partir das linhas e não da contabilidade da própria plataforma:
f = client.datasets.get_freshness(dataset_id).freshness
print(f.as_of, f.age_days, f.status)
const { freshness } = await client.datasets.getFreshness(datasetId);
console.log(freshness.asOf, freshness.ageDays, freshness.status);
No MCP é get_data_freshness, que aceita exatamente um de viz_id ou dataset_id. Com
um viz_id, ele resolve cada conjunto de dados que o painel lê e reporta o mais antigo —
um painel só está tão atual quanto sua entrada mais velha. A rota REST é
GET /v1/datasets/{dataset_id}/freshness.
Os três campos, e por que são três
| Campo | O que é |
|---|---|
asOf | O valor mais recente na própria coluna temporal dos dados. Esta é a resposta, e ela não pode lisonjear: é lida das linhas. ageDays é a idade disso; negativo significa dados com data futura. |
lastLoadedAt | Quando as linhas chegaram pela última vez. Um fato de pipeline, não uma idade — um arquivo carregado hoje de manhã pode não conter nada mais recente que março. Nunca cite isso como data de referência. |
refresh | Se há algo agendado para deixar os números mais novos, incluindo a resposta «nada, e nada pode haver» para um arquivo plano. É o campo que cobre o caso «sem agendamento» descrito acima. |
status diz quanto confiar em asOf: MEASURED (é real), NO_TIME_COLUMN (os dados não
têm coluna de data, portanto não têm data de referência a ter), ou UNKNOWN — que significa
que o fato não foi estabelecido e nunca é uma aprovação.
lastLoadedSource nomeia qual relógio respondeu a lastLoadedAt. iceberg_snapshot é o
horário físico de escrita do snapshot atual da tabela — o único instante de chegada
verdadeiro, e um que uma renomeação não pode mover. not_established significa que
lastLoadedAt é null de propósito: aquele caminho de leitura não consultou o snapshot,
então em vez de responder com o updatedAt da linha — um relógio de escrita que qualquer
edição adianta, e que uma vez reportou linhas chegando nove dias depois do que chegaram — o
campo recusa. Um campo que recusa é estritamente melhor que um que responde com o relógio
errado. A rota acima é a chamada que lê o certo. get_viz e list_vizzes carregam um
dataFreshness gratuito, definido sobre a postura de atualização e UNKNOWN sobre a data de
referência; esta chamada é o que resolve esse UNKNOWN.
O que não é a idade dos dados
Nem publishStatus, nem lastBuild.createdAt, nem processingStatus, nem updatedAt
respondem a esta pergunta. Eles são, em ordem: se um bundle existe, quando o código foi
compilado, se o arquivo foi analisado uma vez, e quando a linha mudou pela última vez.
Cada um deles se lê como atual em um painel cujos números têm semanas. Se você está a
ponto de reportar atualidade a partir de qualquer um deles, chame a rota de frescor em vez
disso.
Custa um SELECT MAX(<coluna de data>) por conjunto de dados — a mesma sondagem que o
painel renderizado já executa ao montar.
Onde vive cada gesto
- As programações são a superfície MCP.
create_refresh,list_refreshes,get_refreshedelete_refreshcobrem todo o ciclo de vida de uma atualização recorrente a partir do seu agente. - "Atualize agora" é o SDK e a API REST. Uma execução avulsa é uma única chamada lá — veja a Referência da API.
- Projetos dbt carregam suas próprias rotas de execução e atualização
(
/v1/dbt/projects/{project_id}/run,/v1/dbt/projects/{project_id}/refreshes).