cassionAnalyse de données

Retour à la leçonLeçon 6 sur 8Sortir les données

Une extraction que l'on peut désigner

Le même diaporama que les téléchargements, rendu sous forme de page. Lancez le diaporama pour le présenter en plein écran — les flèches ou un clic avancent d'une diapositive, Échap quitte.

Diapositives · PDFDiapositives · PowerPoint

  1. Diapositive 1 / 20

    Ce que couvre cette leçon

    • La question à laquelle cette leçon répond
    • La forme
    • Le manifeste
    • Le script
    • Validez à la frontière
    • Comparez à l'extraction précédente
    • Ce que cela remplace
    • La suite
    Notes du présentateur
    Un script, un dossier daté, un manifeste qui consigne ce qui a été demandé et quand, et la réponse brute conservée à côté du tableau propre — pour qu'un chiffre publié en mars soit encore défendable en septembre.
  2. Diapositive 2 / 20

    La question à laquelle cette leçon répond

    • Six mois après avoir publié un taux de couverture de district, quelqu'un relance la requête et obtient autre chose.
    Notes du présentateur
    Six mois après avoir publié un taux de couverture de district, quelqu'un relance la requête et obtient autre chose. Cela arrivera, et il y a quatre raisons légitimes — saisie tardive, dénominateur révisé, formation réaffectée, et tables analytiques qui n'avaient pas tourné. Vous ne pouvez en empêcher aucune. Vous pouvez rendre possible de dire laquelle c'était, et cela tient entièrement à ce que vous avez conservé.
  3. Diapositive 3 / 20

    La forme — Exemple

    extracts/
      2026-07-28/
        manifest.json          what was requested, by whom, when
        raw/
          analytics.json       the response, untouched
          dataValueSets.json
          dataElements.json
          organisationUnits.json
          dataSets.json
          indicators.json
        tidy/
          coverage_by_facility_period.csv
  4. Diapositive 4 / 20

    La forme

    • Un dossier daté par extraction — jamais écrasé
    • La réponse brute est conservée intacte — Si votre analyse syntaxique s'avère fausse, la donnée est toujours là
    • Le manifeste est l'essentiel — Tout le reste s'en récupère
    Notes du présentateur
    Trois règles donnent sa valeur à ce dossier. Un dossier daté par extraction, jamais écrasé. Le disque coûte moins cher que la réunion. La réponse brute est conservée intacte. Si votre analyse syntaxique s'avère fausse, la donnée est toujours là. Un CSV propre est un artefact dérivé, et un artefact dérivé ne se dé-dérive pas. Le manifeste est l'essentiel. Tout le reste s'en récupère.
  5. Diapositive 5 / 20

    Le manifeste — En Python (suite)

    import json
    import datetime as dt
    from pathlib import Path
    
    def write_manifest(folder, request, system_info):
        manifest = {
            "extracted_at": dt.datetime.now(dt.timezone.utc).isoformat(),
            "extracted_by": "me-officer@example.org",
            "instance": request["base_url"],
            "dhis2_version": system_info.get("version"),
            "analytics_last_run": system_info.get("lastAnalyticsTableSuccess"),
            "request": {
                "endpoint": request["endpoint"],
                "dx": request["dx"],
                "ou": request["ou"],
                "pe": request["pe"],
  6. Diapositive 6 / 20

    Le manifeste — En Python (suite)

            },
            "rows_returned": request["rows"],
            "script_version": "extract.py 1.3",
        }
        Path(folder, "manifest.json").write_text(json.dumps(manifest, indent=2))
        return manifest
  7. Diapositive 7 / 20

    Le manifeste — En R

    manifest <- list(
      extracted_at        = format(Sys.time(), "%Y-%m-%dT%H:%M:%SZ", tz = "UTC"),
      instance            = base_url,
      analytics_last_run  = system_info$lastAnalyticsTableSuccess,
      request             = list(endpoint = "analytics", dx = dx, ou = ou, pe = pe),
      rows_returned       = nrow(tidy),
      script_version      = "extract.R 1.3"
    )
    
    jsonlite::write_json(manifest, file.path(folder, "manifest.json"), auto_unbox = TRUE)
    Notes du présentateur
    Deux champs justifient à eux seuls leur place. analytics_last_run dit si une saisie tardive a pu être incluse. pe consigne les périodes exactement demandées, ce qui rend une fenêtre relative comme « les douze derniers mois » reconstructible au lieu d'être la description du jour où le script a tourné. Le reste est la provenance que réclamait déjà le cours Jointures et restructuration, arrivant ici avec un système d'où la tirer.
  8. Diapositive 8 / 20

    Le script — En Python (suite)

    def extract(base_url, dx, ou, pe, token, out_root="extracts"):
        session = requests.Session()
        session.headers["Authorization"] = f"ApiToken {token}"
    
        folder = Path(out_root, dt.date.today().isoformat())
        (folder / "raw").mkdir(parents=True, exist_ok=True)
        (folder / "tidy").mkdir(exist_ok=True)
    
        info = session.get(f"{base_url}/system/info", timeout=60).json()
    
        payload = analytics(dx, ou, pe, session)
        (folder / "raw" / "analytics.json").write_text(json.dumps(payload))
    
        for name, path in METADATA_ENDPOINTS.items():
            meta = session.get(f"{base_url}/{path}", timeout=120).json()
            (folder / "raw" / f"{name}.json").write_text(json.dumps(meta))
  9. Diapositive 9 / 20

    Le script — En Python (suite)

    
        tidy = to_frame(payload)
        tidy.to_csv(folder / "tidy" / "coverage_by_facility_period.csv", index=False)
    
        write_manifest(folder, {"base_url": base_url, "endpoint": "analytics",
                                "dx": dx, "ou": ou, "pe": pe, "rows": len(tidy)}, info)
        return folder
  10. Diapositive 10 / 20

    Le script — En R

    extract <- function(base_url, dx, ou, pe, token) {
      folder <- file.path("extracts", Sys.Date())
      dir.create(file.path(folder, "raw"), recursive = TRUE, showWarnings = FALSE)
      dir.create(file.path(folder, "tidy"), showWarnings = FALSE)
      # ... same shape: system info, analytics, metadata, tidy, manifest
      folder
    }
  11. Diapositive 11 / 20

    Le script

    • La fenêtre est calculée, puis passée en argument — L'appelant décide quels douze mois ; le script les consigne
    Notes du présentateur
    La fenêtre est calculée, puis passée en argument. L'appelant décide quels douze mois ; le script les consigne. Ce seul choix sépare une extraction que l'on peut désigner d'une extraction que l'on peut seulement relancer.
  12. Diapositive 12 / 20

    Le script — En Python

    def last_full_months(n, today=None):
        today = today or dt.date.today()
        first_of_this = today.replace(day=1)
        months = []
        for i in range(n, 0, -1):
            m = first_of_this - dt.timedelta(days=1)
            for _ in range(i - 1):
                m = m.replace(day=1) - dt.timedelta(days=1)
            months.append(m.strftime("%Y%m"))
        return months
  13. Diapositive 13 / 20

    Le script — En R

    last_full_months <- function(n) {
      ends <- seq(Sys.Date(), by = "-1 month", length.out = n + 1)[-1]
      rev(format(ends, "%Y%m"))
    }
    Notes du présentateur
    Notez last_full_months, non last_months. Le mois en cours est toujours incomplet, et l'inclure fait paraître toute tendance en baisse — le piège de la période partielle du cours sur les jointures, arrivant avec un système qui vous la servira volontiers.
  14. Diapositive 14 / 20

    Validez à la frontière — En Python

    def check(tidy, expected_org_units, expected_periods):
        problems = []
        if tidy["ou"].nunique() != expected_org_units:
            problems.append(f"{tidy['ou'].nunique()} org units, expected {expected_org_units}")
        if tidy["pe"].nunique() != expected_periods:
            problems.append(f"{tidy['pe'].nunique()} periods, expected {expected_periods}")
        if tidy["value"].isna().any():
            problems.append("null values in the response")
        return problems
    Notes du présentateur
    L'extraction est une lecture dans le système de quelqu'un d'autre, ce qui en fait exactement le raccord sur lequel le cours de nettoyage posait un contrat.
  15. Diapositive 15 / 20

    Validez à la frontière — En R

    check <- function(tidy, expected_ou, expected_pe) {
      c(if (n_distinct(tidy$ou) != expected_ou) "unexpected org unit count",
        if (n_distinct(tidy$pe) != expected_pe) "unexpected period count")
    }
    Notes du présentateur
    Le plus précieux est le décompte d'unités d'organisation. Une extraction silencieusement plus petite est la défaillance d'API la plus fréquente en pratique — un changement de droits, une réorganisation, une unité fermée — et elle produit un total de district discrètement plus bas, sans rien dans le fichier pour dire pourquoi.
  16. Diapositive 16 / 20

    Comparez à l'extraction précédente — En Python

    def compare(old_folder, new_folder, keys=("ou", "pe", "dx")):
        old = pd.read_csv(Path(old_folder, "tidy", "coverage_by_facility_period.csv"))
        new = pd.read_csv(Path(new_folder, "tidy", "coverage_by_facility_period.csv"))
    
        merged = old.merge(new, on=list(keys), how="outer",
                           suffixes=("_old", "_new"), indicator=True)
        changed = merged[merged["value_old"] != merged["value_new"]]
        return {
            "in_both": int((merged["_merge"] == "both").sum()),
            "new_only": int((merged["_merge"] == "right_only").sum()),
            "gone": int((merged["_merge"] == "left_only").sum()),
            "values_changed": len(changed),
        }
    Notes du présentateur
    Deux dossiers datés rendent une comparaison possible, et c'est là que les quatre explications se séparent.
  17. Diapositive 17 / 20

    Comparez à l'extraction précédente — En R

    compare <- function(old, new) {
      full_join(old, new, by = c("ou", "pe", "dx"), suffix = c("_old", "_new")) |>
        summarise(changed = sum(value_old != value_new, na.rm = TRUE),
                  gone = sum(is.na(value_new)), added = sum(is.na(value_old)))
    }
    Notes du présentateur
    Quatre nombres, et chacun désigne une cause différente. Des valeurs modifiées sur les mêmes clés, c'est une saisie tardive ou une révision. Des lignes disparues, c'est une réaffectation ou un changement de droits. Des lignes ajoutées, c'est du rapportage tardif qui arrive. Et un gros écart avec analytics_last_run qui a bougé entre-temps, ce sont les tables analytiques et non les données. Faites-le chaque mois et conservez la sortie. C'est la série de constats du cours d'évaluation, bâtie sur une source que vous maîtrisez désormais.
  18. Diapositive 18 / 20

    Ce que cela remplace — Exemple

    Before:  a spreadsheet in an email, monthly, method unknown
    After:   extracts/2026-07-28/, one command, manifest, raw responses,
             a diff against last month, and a figure you can defend in September
    Notes du présentateur
    Le script fait environ quatre-vingts lignes. C'est le morceau de code au meilleur rendement de ce cours, et celui que la plupart des équipes n'écrivent jamais parce que l'export manuel marche très bien le jour même.
  19. Diapositive 19 / 20

    La suite

    • Vous avez un chiffre de routine que vous pouvez désigner.
    Notes du présentateur
    Vous avez un chiffre de routine que vous pouvez désigner. La dernière unité le pose à côté d'une estimation d'enquête portant sur la même chose — trois nombres pour la malnutrition aiguë, de 8,7 % à 14,9 % — et décompose l'écart selon les deux causes qui le produisent réellement.
  20. Diapositive 20 / 20

    La suite

    Lire la leçon complète, avec le code exécutable Retour à la leçon