Aller au contenu

Comment fonctionne le calcul automatique des indicateurs

Explication donnée à Bernard le 2026-07-09, à sa demande, sur l'implémentation du chantier précédent (voir ProjetESM/debug/execution-sous-activites-indicateurs).

Architecture — 4 pièces qui s'emboîtent

1. Le déclencheur — CalculerMesuresAutomatiquesUseCase

Un seul use case, appelé pour une période donnée (ex: "2026-07"). Il boucle sur les 43 indicateurs du référentiel (IndicateurSuiviRepository.lister()), et pour chacun cherche s'il existe un calculateur enregistré sous son code exact. S'il n'y en a pas, il marque NON_CALCULABLE et passe au suivant — jamais d'erreur bloquante, chaque indicateur est indépendant. À la fin, toutes les valeurs réussies sont écrites en une seule transaction batch (upsertMesuresBatch).

// BackEnd/src/Modules/Pilotage/application/usecases/CalculerMesuresAutomatiquesUseCase.ts:78-101
const calculateur = this.registreCalculateurs[indicateur.code];
if (!calculateur) { details.push({ code, succes: false, erreur: 'NON_CALCULABLE' }); continue; }
const valeur = await calculateur.calculer(fin);

2. Le registre — RegistreCalculateursIndicateurs.ts

Une fonction fabrique qui retourne un objet { code → calculateur }. C'est du code écrit à la main, un calculateur par indicateur — délibérément pas un moteur de règles générique paramétrable. Décision de conception discutée avec Bernard : construire un moteur générique aurait fait sens pour la vision "SERA" (plateforme configurable multi-projets), mais pour ce projet précis avec 43 indicateurs fixes définis par le cadre logique du bailleur, un registre explicite est plus simple à lire, déboguer et faire évoluer qu'une couche d'abstraction supplémentaire.

3. Les deux formes — CalculateurFactories.ts

Tous les indicateurs se ramènent à deux formes : un comptage (un total, éventuellement désagrégé homme/femme) ou un taux (un ratio numérateur/dénominateur, formaté en %). Deux factories génériques produisent l'objet ICalculateurIndicateur à partir d'une simple fonction de requête :

'1.1.2.1': creerCalculateurComptage(false, (fin) => comptageSupportsCommunication(prisma, fin)),
'1.2.1.2': creerCalculateurTaux(true, (fin) => tauxParticipationReunionsComite(prisma, fin)),

Chaque indicateur n'écrit que sa requête Prisma spécifique — toute la logique de formatage/désagrégation par genre est mutualisée dans les deux factories.

// Comptage simple — somme de la quantité produite jusqu'à la fin de période
async function comptageSupportsCommunication(prisma, fin) {
  const agg = await prisma.supportCommunication.aggregate({
    _sum: { quantiteProduite: true },
    where: { OR: [{ dateProduction: null }, { dateProduction: { lte: fin } }] },
  });
  return { total: agg._sum.quantiteProduite ?? 0 };
}

// Taux avec désagrégation par genre — présence nominative aux réunions de comité
async function tauxParticipationReunionsComite(prisma, fin) {
  const presences = await prisma.presenceReunionComite.findMany({
    where: { reunion: { dateReunion: { lte: fin } } },
    select: { present: true, membre: { select: { genre: true } } },
  });
  // ... calcule numérateur/dénominateur global + par genre
}

4. Le principe du cumul

Chaque mesure représente le cumulé depuis le début du projet jusqu'à la fin de la période, jamais une simple fenêtre mensuelle — c'est le standard MEAL habituel pour ce type de cadre logique. D'où finDePeriode("2026-07") qui calcule juste la borne supérieure (dernier instant de juillet), et toutes les requêtes filtrent en <= fin, sans borne inférieure.

Les 4 indicateurs volontairement non calculables

4 indicateurs restent NON_CALCULABLE faute de source de données non ambiguë dans le schéma actuel. Libellés exacts issus de prisma/seeds/seed.ts :

Code Libellé officiel Pourquoi non calculable
1.2.2.3 Nombre d'activités de cohésion sociale identifiées et réalisées Aucun modèle Prisma dédié pour tracer ce type précis d'activité
2.1.2.2 Proportion de maîtres coraniques engagés comme acteurs communautaires dans la lutte contre la mendicité des enfants Aucun champ sur MaitreCoranique ne trace cet « engagement » comme acteur communautaire
2.1.3.1 Nombre de leaders religieux et maîtres coraniques sensibilisés par les pairs sur les droits et devoirs des enfants et la loi contre la mendicité EducateurPair n'est pas relié à FormationSensibilisation.animateur — impossible de savoir si une sensibilisation donnée a été faite « par les pairs »
2.2.2.2 Nombre de comités techniques créés et fonctionnels Aucun modèle Prisma dédié — les « comités techniques » ne sont pas distingués des comités villageois dans le schéma actuel

Les trois derniers partagent le même problème de fond : une notion qualitative du cadre logique (« engagé comme acteur », « sensibilisé par les pairs », « comité technique » distinct du comité villageois) qui n'a pas d'équivalent structurel dans le modèle de données — il faudrait soit ajouter un champ/une relation dédiée, soit accepter que ces 4 indicateurs restent en saisie manuelle ou en évaluation qualitative plutôt qu'en comptage automatique.

Par ailleurs, les indicateurs d'EFFET/RÉSULTAT (impact) sont exclus par choix méthodologique — ce sont des indicateurs d'enquête/évaluation finale, pas des comptages dérivables du quotidien. Tout est documenté en commentaire en tête du registre (RegistreCalculateursIndicateurs.ts) pour que ce ne soit pas pris pour un oubli plus tard.

Liens connexes