cassionAnalyse de données

Leçon 5 sur 8

Unité · La charge de dossiers

Trente-trois dossiers chacun

Les orientations situent une charge active VBG autour de 25 dossiers par travailleur. Une zone est ici en moyenne à 33,2 et culmine à 51. Elle tient aussi 1,78 revue de plan par dossier contre 2,45, et perd le contact avec 38,9 % des dossiers qu'elle clôt. Trois tableaux, une seule cause.

PythonR120 minNorme humanitaire fondamentale (CHS)Définitions d'indicateurs de l'UNICEFStandards Sphère

La charge de dossiers est un stock, non un flux

Un compte de dossiers est un flux : combien de dossiers se sont ouverts ce mois-ci. Une charge de dossiers est un stock : combien sont ouverts à l’instant, et c’est le nombre que vit un travailleur social.

Le calculer suppose d’étendre chaque dossier sur les mois où il a été ouvert.

import pandas as pd

cases = pd.read_csv("protection-case-management-2024.v1.csv")
CUTOFF = 12

def months_open(row):
    opened = int(row["opened_month"][5:7])
    closed = int(row["closed_month"][5:7]) if pd.notna(row["closed_month"]) else CUTOFF
    return range(opened, max(closed, opened) + 1)

active = [
    {"caseworker_id": row["caseworker_id"], "admin2": row["admin2"], "month": month}
    for _, row in cases.iterrows()
    for month in months_open(row)
]
active = pd.DataFrame(active)

caseload = active.groupby(["caseworker_id", "month"]).size().rename("open_cases")
print(caseload.describe().round(1))
library(dplyr)

cases |>
  mutate(opened = as.integer(substr(opened_month, 6, 7)),
         closed = coalesce(as.integer(substr(closed_month, 6, 7)), 12L)) |>
  rowwise() |>
  mutate(month = list(seq(opened, max(closed, opened)))) |>
  tidyr::unnest(month) |>
  count(caseworker_id, month, name = "open_cases")

C’est l’expansion en personnes-temps du premier cours du module 4, appliquée à un travailleur social plutôt qu’à un patient. Un dossier ouvert de mars à août contribue à six charges mensuelles, et un registre lu ligne à ligne ne le montre jamais.

Le nombre autour duquel les orientations sont écrites

by_area = caseload.reset_index().merge(
    cases[["caseworker_id", "admin2"]].drop_duplicates("caseworker_id"),
    on="caseworker_id",
)
summary = by_area.groupby("admin2")["open_cases"].agg(["mean", "max"]).round(1)
print(summary.sort_values("mean", ascending=False))
# Mean and peak monthly caseload per area.
Zone Charge moyenne Pic
Port-de-Paix 33,2 51
Gonaïves 28,2 41
Saint-Marc 24,7 43
Hinche 20,7 44
Mirebalais 18,7 32
Saint-Louis-du-Nord 14,8 26

Les orientations de gestion de cas VBG situent la charge active autour de 25. Port-de-Paix est au-dessus toute l’année et culmine au double ; Saint-Louis-du-Nord est nettement en dessous.

Rapportez la moyenne et le pic. Un travailleur à 51 dossiers ouverts un mois donné dispose, ce mois-là, d’environ quatre heures de travail par dossier déplacement et documentation compris, et une moyenne annuelle de 33 le dissimule.

Ce que fait une charge excessive

La raison pour laquelle la charge a un seuil est que des choses cèdent lorsqu’il est dépassé, et ce registre permet d’en observer deux céder ensemble.

quality = cases.groupby("admin2").agg(
    cases=("case_id", "size"),
    reviews_per_case=("case_plan_reviews", "mean"),
)
closed = cases[cases["closure_reason"].notna()]
quality["lost_contact"] = closed.groupby("admin2")["closure_reason"].apply(
    lambda s: (s == "lost-contact").mean()
)
print(quality.round(2).sort_values("reviews_per_case"))
cases |>
  summarise(n = n(), reviews = mean(case_plan_reviews),
            lost = mean(closure_reason == "lost-contact", na.rm = TRUE),
            .by = admin2)
Zone Charge Revues par dossier Perte de contact
Saint-Louis-du-Nord 14,8 1,74 26,0 %
Port-de-Paix 33,2 1,78 38,9 %
Mirebalais 18,7 2,10 30,8 %
Gonaïves 28,2 2,31 24,7 %
Saint-Marc 24,7 2,38 22,1 %
Hinche 20,7 2,45 20,2 %

Port-de-Paix porte la charge la plus élevée, tient parmi les moins de revues de plan, et perd le contact avec la plus grande part des dossiers qu’elle clôt. Trois tableaux différents produits par une seule cause, et le tableau de charge est celui qui nomme la cause.

Notez que Saint-Louis-du-Nord tient aussi peu de revues sur une charge faible — les revues par dossier ne sont donc pas une fonction propre de la charge, et la lecture honnête est que Port-de-Paix a un problème de charge tandis que Saint-Louis-du-Nord en a peut-être un autre. Un motif qui colle à trois zones et pas à la quatrième reste un motif, et dire quelle zone ne colle pas fait partie de son rapportage.

L’erreur d’effectif

areas_per_worker = cases.groupby("caseworker_id")["admin2"].nunique()
print(areas_per_worker[areas_per_worker > 1])
cases |> summarise(areas = n_distinct(admin2), .by = caseworker_id) |>
  filter(areas > 1)

Un identifiant de travailleur social apparaît sous deux zones, car un agent muté a été réenregistré plutôt que déplacé.

Une charge calculée par agent est juste. Une charge calculée par agent et par zone est fausse, car elle répartit la charge réelle d’une personne sur deux lignes et fait paraître les deux gérables. Décidez de ce que signifie l’identifiant avant de grouper dessus, et si le registre ne peut pas vous le dire, c’est une question pour le bureau et non une hypothèse pour l’analyste.

Ce que la charge n’est pas

Ce n’est pas une mesure de productivité. Un agent à 40 dossiers ouverts ne travaille pas plus dur qu’un agent à 15 ; il est plus susceptible de faire défaut à 40 personnes lentement. Employer la charge pour classer les agents inverse la raison d’être de l’indicateur.

Elle n’est pas comparable entre types de dossiers. Un dossier de protection de l’enfance avec un plan impliquant une école et un tuteur n’équivaut pas à un référencement juridique ponctuel, et le champ case_category de ce registre est ce qui permet de les pondérer.

mix = cases.groupby(["admin2", "case_category"]).size().unstack(fill_value=0)
print((mix.div(mix.sum(axis=1), axis=0) * 100).round(1))
cases |> count(admin2, case_category) |>
  mutate(share = n / sum(n), .by = admin2)

Vérifiez la composition des dossiers avant de comparer des charges. Une zone avec davantage de dossiers de protection de l’enfance porte plus de travail par dossier, et une comparaison brute la pénalise.

Rapportez-la comme un tableau de supervision

Caseload, 2024, 1,108 cases across 17 caseworkers

  Area                  Mean   Peak   Reviews/case   Lost contact
  Port-de-Paix          33.2     51           1.78          38.9%
  Gonaives              28.2     41           2.31          24.7%
  Saint-Marc            24.7     43           2.38          22.1%
  Hinche                20.7     44           2.45          20.2%
  Mirebalais            18.7     32           2.10          30.8%
  Saint-Louis-du-Nord   14.8     26           1.74          26.0%

  Guidance: active caseload around 25 cases per worker.
  One caseworker identifier appears in two areas after a transfer; caseload
  is computed per worker, not per worker per area.

  Port-de-Paix is above the guideline all year and shows the pattern that
  follows: fewest case plan reviews and most cases closed for lost contact.

Mettez l’orientation dans le tableau. Une charge de 33 ne dit rien à un lecteur qui ignore ce que vaut 25, et toute la raison d’être de la ligne est la comparaison.

La suite

Les dossiers se clôturent, et le temps qu’ils prennent est l’autre nombre sur lequel un superviseur est jugé. La leçon suivante porte sur cette mesure, et sur les 42,6 % de dossiers non encore clos qui la biaiseraient si on les ignorait.

Animer cette leçon

La leçon en diaporama, la prose étant reléguée dans les notes du présentateur plutôt que projetée. Produit à partir de cette page, dont il ne peut donc pas s'écarter.

Lancer le diaporamaLire les diapositives

Le PDF ne requiert aucun logiciel et se projette depuis n'importe quel poste. Le fichier PowerPoint est fait pour être modifié : appliquez la charte de votre organisation, retirez une section pour une séance plus courte, ou fusionnez deux leçons en atelier.