Leçon 5 sur 8
Unité · Sortir les données
Interroger le système au lieu d'exporter à la main
Deux points d'accès qui répondent à des questions différentes, la syntaxe de dimensions qui adresse un cube, et les quatre choses à régler avant tout — authentification, pagination, limites de débit, et le fait que les tables analytiques sont périmées tant que personne ne les reconstruit.
L’export mensuel est le problème
Quelqu’un ouvre l’interface, choisit les unités d’organisation, les périodes, les éléments de données, clique sur exporter, et envoie un tableur par courriel. Le mois suivant, quelqu’un recommence, légèrement différemment, et personne ne sait dire comment.
C’est ce processus que cette leçon remplace. Non parce que l’interface est mauvaise — c’est ainsi que la plupart des gens devraient employer DHIS2 — mais parce qu’une analyse qui commence par un fichier bâti à la main n’est pas reproductible, et la reproduire était tout l’objet du module 2.
Deux points d’accès, deux questions
La chose la plus utile à savoir sur l’API est qu’il existe deux façons de sortir des données et qu’elles répondent à des questions différentes.
| Point d’accès | Renvoie | À employer pour |
|---|---|---|
/api/dataValueSets |
Les valeurs brutes stockées, telles que saisies | Reproduire le registre ; tout ce qui exige de voir ce qui a été tapé |
/api/analytics |
Des valeurs agrégées et calculées | Indicateurs, totaux remontés dans la hiérarchie, tout ce que le système calcule |
La différence compte plus qu’il n’y paraît.
dataValueSets donne la vérité telle que stockée — une ligne par élément de
données, unité d’organisation, période et combinaison d’options de catégories.
Aucune agrégation, aucune arithmétique d’indicateur, aucun remplissage. C’est ce
qu’il vous faut quand l’analyse doit être défendable jusqu’à la saisie.
analytics donne la réponse que le système afficherait sur un tableau de bord
— agrégée dans la hiérarchie, indicateurs évalués, opérateurs d’agrégation
appliqués. C’est ce qu’il vous faut quand il vous faut le même chiffre que celui
que regarde le ministère.
Tirez les deux quand ils sont censés concorder. Là où ils divergent, l’écart est une règle d’agrégation dont vous n’avez pas tenu compte, et la trouver vaut une après-midi.
Adresser le cube
Les requêtes analytics sont dimensionnelles. Vous demandez une tranche de cube,
et chaque dimension porte un nom.
GET /api/analytics.json
?dimension=dx:PENTA3_UID;BCG_UID
&dimension=ou:LEVEL-4;DISTRICT_UID
&dimension=pe:202401;202402;202403
&displayProperty=NAME
dx— dimension de données : éléments de données, indicateurs, opérandes.ou— unités d’organisation.LEVEL-4désigne toutes les unités du niveau 4 ; y adjoindre l’UID d’un district restreint à ce sous-arbre.pe— périodes, explicites (202401) ou relatives (LAST_12_MONTHS).
Préférez les périodes explicites aux relatives dans un script.
LAST_12_MONTHS renvoie une fenêtre différente chaque mois, ce qui est commode
pour un tableau de bord et fatal pour la reproductibilité. Calculez la fenêtre dans
votre code, puis demandez-la nommément, pour que la requête elle-même consigne ce
qui a été demandé.
import requests
BASE = "https://play.dhis2.org/40/api"
def analytics(dx, ou, pe, session):
response = session.get(
f"{BASE}/analytics.json",
params={
"dimension": [f"dx:{';'.join(dx)}", f"ou:{ou}", f"pe:{';'.join(pe)}"],
"displayProperty": "NAME",
"skipMeta": "false",
},
timeout=120,
)
response.raise_for_status()
return response.json()
library(httr2)
analytics <- function(dx, ou, pe) {
request("https://play.dhis2.org/40/api/analytics.json") |>
req_url_query(
dimension = paste0("dx:", paste(dx, collapse = ";")),
dimension = paste0("ou:", ou),
dimension = paste0("pe:", paste(pe, collapse = ";")),
displayProperty = "NAME",
.multi = "explode"
) |>
req_perform() |>
resp_body_json()
}
skipMeta=false est délibéré. Le bloc de métadonnées porte les noms derrière
chaque UID de la réponse, et sans lui vous avez un tableau d’identifiants que
personne ne peut lire.
La forme de la réponse
analytics renvoie un bloc d’en-têtes, un tableau de lignes et un dictionnaire de
métadonnées. Transformez-le en tableau immédiatement et joignez les noms.
def to_frame(payload):
import pandas as pd
columns = [h["name"] for h in payload["headers"]]
frame = pd.DataFrame(payload["rows"], columns=columns)
names = {uid: item["name"]
for uid, item in payload["metaData"]["items"].items()}
for column in ("dx", "ou", "pe"):
if column in frame:
frame[f"{column}_name"] = frame[column].map(names)
return frame
to_frame <- function(payload) {
cols <- vapply(payload$headers, \(h) h$name, character(1))
rows <- do.call(rbind, lapply(payload$rows, \(r) unlist(r)))
as.data.frame(rows, stringsAsFactors = FALSE) |> setNames(cols)
}
Conservez les UID en plus des noms. Les noms changent ; les UID non. Un script qui joint sur un nom casse à la première correction orthographique, et il casse en silence, en renvoyant moins de lignes.
Les quatre choses à régler d’abord
L’authentification. Employez un jeton d’accès personnel là où l’instance le permet, l’authentification de base sinon, et ne mettez jamais l’un ni l’autre dans le script. Une variable d’environnement ou un fichier d’identifiants hors du dépôt, toujours.
import os
session = requests.Session()
session.headers["Authorization"] = f"ApiToken {os.environ['DHIS2_TOKEN']}"
req_headers(request(url), Authorization = paste("ApiToken", Sys.getenv("DHIS2_TOKEN")))
Un dépôt d’analyse contenant un identifiant valide pour un système d’information sanitaire national est un incident grave, et cela s’est produit assez souvent pour que ce soit la première chose qu’un relecteur doive chercher.
La pagination. Les points d’accès de métadonnées paginent par défaut à 50. Les points d’accès de données renverront volontiers des millions de lignes et expireront avant.
def paged(url, session, page_size=1000):
page = 1
while True:
payload = session.get(url, params={"page": page, "pageSize": page_size},
timeout=120).json()
yield payload
pager = payload.get("pager", {})
if page >= pager.get("pageCount", 1):
return
page += 1
# Same idea: loop until page == pageCount, and never assume one request is all of it.
Le débit et la taille. Demandez un district et une année à la fois, non le pays et cinq années. Une requête qui répond en huit secondes vaut mieux qu’une qui expire à quatre-vingt-dix, et la boucle fait trois lignes. Soyez un bon citoyen d’un serveur où travaillent aussi des agents de saisie.
Les tables analytiques sont périmées. C’est celle qui surprend. analytics lit
des tables pré-agrégées reconstruites selon un calendrier, en général nocturne.
Une donnée saisie aujourd’hui n’est pas dans analytics tant que les tables
n’ont pas tourné. dataValueSets la voit immédiatement.
Les deux points d’accès peuvent donc légitimement diverger, et la bonne réaction n’est pas d’ouvrir un ticket. Vérifiez la dernière exécution.
info = session.get(f"{BASE}/system/info", timeout=60).json()
print(info.get("lastAnalyticsTableSuccess"))
resp <- request(paste0(base, "/system/info")) |> req_perform() |> resp_body_json()
resp$lastAnalyticsTableSuccess
Consignez cet horodatage avec chaque extraction. C’est la différence entre « les chiffres ont changé » et « les chiffres ont changé parce que les tables analytiques n’avaient pas tourné quand j’ai tiré ».
Tirez aussi les métadonnées
Tout ce que la leçon 1 réclamait est un point d’accès.
/api/dataElements.json?fields=id,name,aggregationType,categoryCombo[id,name]
/api/organisationUnits.json?fields=id,name,level,parent[id]&paging=false
/api/dataSets.json?fields=id,name,periodType,organisationUnits~size
/api/indicators.json?fields=id,name,numerator,denominator,indicatorType[name]
Quatre requêtes. Elles vous donnent l’opérateur d’agrégation, la hiérarchie figée, le dénominateur des rapports attendus et les définitions d’indicateurs — soit toutes les questions que les leçons précédentes disaient qu’il faudrait poser à un collègue.
Enregistrez-les à côté des données, et la leçon suivante rend cela automatique.
La suite
Vous savez poser une question au système. La leçon suivante rend cette interrogation reproductible — un script, une fenêtre énoncée, les métadonnées tirées en même temps, et un manifeste qui consigne exactement ce qui a été demandé et quand.