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.