# Garder vos données fraîches

Un tableau de bord ne vaut son URL que si les chiffres qu'il affiche sont à jour. C'est la partie qu'une session d'agent ne peut pas laisser derrière elle : votre agent construit le tableau de bord puis s'en va, et les données sous-jacentes doivent continuer d'arriver selon un planning, pendant des mois, sans que personne ne soit dans la boucle.

La fraîcheur est donc un état de plateforme, pas du code de tableau de bord. Vous la configurez une fois contre un jeu de données ; elle continue de fonctionner que quelqu'un maintienne ou non une session ouverte.

## Les trois façons de faire avancer les données[​](#les-trois-façons-de-faire-avancer-les-données "Lien direct vers Les trois façons de faire avancer les données")

|                                | Ce que c'est                                                  | Qui le déclenche                                                          |
| ------------------------------ | ------------------------------------------------------------- | ------------------------------------------------------------------------- |
| **Actualisation à la demande** | Retirer un jeu de données maintenant                          | Vous, votre agent, ou votre CI — `POST /v1/datasets/{dataset_id}/refresh` |
| **Chargement incrémental**     | Ne récupérer que ce qui a changé depuis la dernière exécution | Le même appel, avec `mode: "incremental"`                                 |
| **Actualisation planifiée**    | Une expression cron que la plateforme exécute pour vous       | Personne — le poller cron de la plateforme                                |

## Actualiser un jeu de données maintenant[​](#refresh-a-dataset-now "Lien direct vers Actualiser un jeu de données maintenant")

```
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);
```

Deux résultats possibles, et la différence compte :

* **200 OK** — le travail s'est réellement terminé au cours de la requête. `completed` est `true` et `dataset` est la ligne finale. C'est ce qui se passe pour un re-téléchargement de jeu de données public.
* **202 Accepted** — l'actualisation a été *démarrée*. La ligne est verrouillée et marquée `PROCESSING`, et un connecteur s'exécute quelques secondes plus tard. `completed` est `false`. Sondez le jeu de données, ou écoutez le websocket, jusqu'à ce que `processingStatus` sorte de `PROCESSING`.

Un `202` n'est ni un échec ni une réussite — c'est une promesse. Traitez « l'appel est revenu » et « les données sont à jour » comme deux faits différents, et vérifiez le second avant de dire à quelqu'un que le tableau de bord est à jour. L'appel qui le vérifie est [`get_data_freshness`](#how-old-are-the-numbers) — tout ce qui précède cette section concerne la circulation des données, pas leur ancienneté.

## Chargements incrémentaux, et l'exécution qui n'est pas encore incrémentale[​](#chargements-incrémentaux-et-lexécution-qui-nest-pas-encore-incrémentale "Lien direct vers Chargements incrémentaux, et l'exécution qui n'est pas encore incrémentale")

Passez un mode pour ne récupérer que les nouvelles lignes :

```
from vibedasher.models import RefreshRequest



result = client.datasets.refresh(dataset_id, RefreshRequest(mode="incremental"))

print(result.requested_mode, "->", result.effective_mode)
```

`requestedMode` renvoie ce que vous avez demandé. **`effectiveMode` est ce qui s'est réellement exécuté, et ce n'est pas toujours la même chose.** Une demande incrémentale contre un jeu de données sans point de contrôle existant renvoie `effectiveMode: "bootstrap"` : elle s'exécute comme un écrasement complet *et* enregistre le filigrane, si bien que l'exécution **suivante** est la première véritablement incrémentale.

Basez votre logique sur `effectiveMode`, jamais sur ce que vous avez demandé. (C'est la même règle que pour `client.query`, qui remonte la voie d'exécution réellement utilisée plutôt que celle que vous avez demandée — un principe qui mérite d'être intériorisé : la plateforme vous dit ce qui s'est passé, et c'est à vous de le lire.)

Pour forcer la prochaine exécution à tout récupérer à nouveau, réinitialisez le filigrane :

```
client.datasets.reset_checkpoint(dataset_id)
```

## Actualisation planifiée[​](#actualisation-planifiée "Lien direct vers Actualisation planifiée")

Une planification est une ligne que vous créez une fois. Elle porte une expression cron et le projet dbt qu'elle actualise :

```
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)
```

La réponse porte un bloc `scheduleRegistration`. **Vérifiez `registered`.** Il est `true` seulement quand la planification est réellement active ; quand il est `false`, `reason` et `message` expliquent pourquoi, et `nextRun` est nul.

Ce champ existe à cause d'une vraie panne, et l'histoire est le meilleur argument en faveur de la conception actuelle. L'implémentation d'origine enregistrait chaque planification comme sa propre règle cloud, enveloppait l'appel dans un `except` large, et répondait `201 Created` avec un `nextRun` confiant. Le rôle de l'API n'avait pas la permission de créer ces règles, si bien que **rien n'a jamais été enregistré** — et chaque client a été informé que ses données seraient actualisées. Cela a été découvert le 2026‑09‑05 : aucune règle n'existait, et la seule planification en base avait un `nextRun` vieux de cinq mois et un nombre d'exécutions de zéro.

Le correctif n'a pas été une meilleure gestion des erreurs. Ce fut la suppression de la chose qui pouvait échouer : **la ligne *est* la planification.** Un poller cron s'exécute toutes les cinq minutes, réclame chaque ligne due par compare-and-set, et l'exécute. Il n'y a pas d'enregistrement externe qui puisse réussir ou échouer, donc la seule chose qui peut mal se passer à la création est une expression cron qui ne peut pas être évaluée — et c'est désormais rejeté d'emblée, pas au moment du déclenchement.

Deux conséquences à prévoir :

* Une planification se déclenche **dans environ cinq minutes** après son heure prévue, pas à la seconde près. Cron signifie ici « pas avant », pas « exactement à ».
* `scheduleRegistration.backend` affiche `db_cron`. Il est conservé dans la réponse parce que des clients s'appuient dessus.

## Lire ce qui s'est passé[​](#lire-ce-qui-sest-passé "Lien direct vers Lire ce qui s'est passé")

Chaque ligne de planification porte son propre historique — `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` est le champ à surveiller. Une planification qui existe depuis une semaine avec `executionCount: 0` n'est pas lente — elle ne s'exécute pas, et c'est exactement l'échec que la conception ci-dessus a été construite pour rendre visible plutôt que silencieux.

## Quel âge ont ces chiffres ?[​](#how-old-are-the-numbers "Lien direct vers Quel âge ont ces chiffres ?")

Tout ce qui précède répond à *« le pipeline fonctionne-t-il ? »*. Cela ne répond pas à *« ce que montre ce tableau de bord est-il à jour ? »*, et les deux divergent dans les deux sens :

* Une planification peut s'exécuter parfaitement et ne rien charger de nouveau. `executionCount` augmente, `lastStatus` vaut `success`, et la ligne la plus récente date toujours de mars.
* **Un jeu de données peut n'avoir aucune planification**, et alors `executionCount` n'a aucune ligne à lire. Un import statique est le cas courant : ses lignes ne changent que lorsque quelqu'un en téléverse de nouvelles, donc une liste de planifications en bonne santé peut coexister avec un jeu de données vieux de neuf jours sans rien en dire. L'absence de planification n'est pas une planification au compteur défaillant.

Un seul appel répond à la vraie question du lecteur, mesurée sur les lignes plutôt que sur la comptabilité interne de la plateforme :

```
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);
```

En MCP il s'agit de `get_data_freshness`, qui prend **exactement un** de `viz_id` ou `dataset_id`. Avec un `viz_id`, il résout chaque jeu de données lu par le tableau de bord et renvoie **le plus ancien** — un tableau de bord n'est jamais plus frais que son entrée la plus vieille. La route REST est `GET /v1/datasets/{dataset_id}/freshness`.

### Les trois champs, et pourquoi ils sont trois[​](#les-trois-champs-et-pourquoi-ils-sont-trois "Lien direct vers Les trois champs, et pourquoi ils sont trois")

| Champ          | Ce que c'est                                                                                                                                                                                                                                            |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `asOf`         | La valeur la plus récente dans la **colonne temporelle des données elles-mêmes**. C'est la réponse, et elle ne peut pas flatter : elle est lue dans les lignes. `ageDays` en donne l'ancienneté ; une valeur négative signifie des données post-datées. |
| `lastLoadedAt` | Quand les lignes ont **atterri** pour la dernière fois. Un fait de pipeline, pas un âge — un fichier chargé ce matin peut ne rien contenir de plus récent que mars. Ne le citez jamais comme date de référence.                                         |
| `refresh`      | Si quelque chose est planifié pour rafraîchir les chiffres, **y compris la réponse « rien, et rien ne peut l'être »** pour un fichier plat. C'est le champ qui couvre le cas « aucune planification » décrit plus haut.                                 |

`status` indique quelle confiance accorder à `asOf` : `MEASURED` (il est réel), `NO_TIME_COLUMN` (les données ne portent aucune colonne de date, donc n'ont pas de date de référence à avoir), ou `UNKNOWN` — qui signifie que le fait n'a pas été établi et n'est **jamais** un succès.

`lastLoadedSource` nomme quelle horloge a répondu à `lastLoadedAt`. `iceberg_snapshot` est l'heure d'écriture physique de l'instantané courant de la table — le seul véritable instant d'atterrissage, et qu'un renommage ne peut pas déplacer. **`not_established` signifie que `lastLoadedAt` vaut `null` volontairement** : ce chemin de lecture n'a pas consulté l'instantané, donc plutôt que de répondre avec l'`updatedAt` de la ligne — une horloge d'écriture que toute modification fait avancer, et qui a une fois signalé des lignes atterries neuf jours après les faits — le champ renonce. Un champ qui renonce vaut strictement mieux qu'un champ qui répond avec la mauvaise horloge. La route ci-dessus est l'appel qui lit la bonne. `get_viz` et `list_vizzes` portent un `dataFreshness` gratuit, précis sur la posture de rafraîchissement et `UNKNOWN` sur la date de référence ; cet appel est ce qui résout cet `UNKNOWN`.

### Ce qui n'est *pas* l'âge des données[​](#ce-qui-nest-pas-lâge-des-données "Lien direct vers ce-qui-nest-pas-lâge-des-données")

Ni `publishStatus`, ni `lastBuild.createdAt`, ni `processingStatus`, ni `updatedAt` ne répondent à cette question. Ils indiquent, dans l'ordre : si un bundle existe, quand le **code** a été compilé, si le fichier s'est analysé une fois, et quand la **ligne** a changé pour la dernière fois. Chacun d'eux paraît *à jour* sur un tableau de bord dont les chiffres ont des semaines. Si vous êtes sur le point de rapporter une fraîcheur à partir de l'un d'eux, appelez plutôt la route de fraîcheur.

Cela coûte un `SELECT MAX(<colonne de date>)` par jeu de données — la même sonde que le tableau de bord rendu exécute déjà à son montage.

## Où vit chaque geste[​](#où-vit-chaque-geste "Lien direct vers Où vit chaque geste")

* **Les planifications sont la surface MCP.** `create_refresh`, `list_refreshes`, `get_refresh` et `delete_refresh` couvrent tout le cycle de vie d'une actualisation récurrente depuis votre agent.
* **« Actualise maintenant » passe par le SDK et l'API REST.** Une exécution ponctuelle y tient en un seul appel — voir la **[Référence API](/fr/reference/vibedasher-sdk-api.md)**.
* **Les projets dbt** portent leurs propres routes d'exécution et d'actualisation (`/v1/dbt/projects/{project_id}/run`, `/v1/dbt/projects/{project_id}/refreshes`).
