Passage automatique des niveaux lycéens
Automatisation du champ users.niveau pour les lycéens.
Les passages sont loggés dans user-niveau-history (userId, oldNiveau, newNiveau, reason, createdAt).
Valeurs possibles
Seconde → Première → Terminale → En transition → Post-bac
null = utilisateur non lycéen, ou lycéen dont le niveau n'a pas encore été renseigné.
Algo 1 — progressAnnualNiveaux (01/08 chaque année)
Cron : 0 0 1 7 * (lib cron v2, mois 0-indexé : 7 = août)
Population ciblée : tous les lycéens (roles @> ['lyceen']) avec un niveau renseigné.
Transitions :
Seconde → Première
Première → Terminale
Terminale → En transition
Ordre d'exécution : décroissant (Terminale en premier, Seconde en dernier). Sans cet ordre, un lycéen en Seconde serait promu Première puis Terminale dans la même passe.
Chaque transition est atomique : UPDATE + INSERT dans user-niveau-history dans une transaction.
Reason : cron_aout.
Algo 2 — progressEnTransitionToPostbac 01/12 (non-MVLS MATCHE)
Cron : 0 0 1 11 * (lib cron v2, mois 0-indexé : 11 = décembre)
Population ciblée : lycéens avec niveau = 'En transition' qui ne sont pas MVLS inscrits avec un binôme actif.
Condition d'exclusion MVLS :
NOT EXISTS (
SELECT 1 FROM "mvls-lyceens" ml
WHERE ml."userId" = "users"."id"
AND ml."charte" = true
AND ml."statut" = 'MATCHE'
AND ml."deletedAt" IS NULL
)
Transition : En transition → Post-bac
Reason : cron_decembre.
Algo 3 — progressEnTransitionToPostbac 10/01 (MVLS MATCHE)
Cron : 0 0 10 0 * (lib cron v2, mois 0-indexé : 0 = janvier)
Population ciblée : lycéens avec niveau = 'En transition' qui sont MVLS inscrits avec un binôme actif, c'est-à-dire ceux qui ont un enregistrement dans mvls-lyceens avec charte = true ET statut = 'MATCHE'.
charte = true= soumission finale du questionnaire MVLS effectuée.statut = 'MATCHE'= binôme trouvé et actif (alimenté par dema1n via RabbitMQ). Ne pas confondre avecusers.mvls = 'mvls'qui est posé dès le début du questionnaire — voirdocs/mvls.md.
Transition : En transition → Post-bac
Reason : cron_janvier.
Backfill (one-shot, déclenché manuellement)
Endpoint admin : POST /admin/niveau/backfill
Deux étapes exécutées dans l'ordre, idempotentes :
Étape 1 — depuis parcours-lyceen
Pour les lycéens avec niveau IS NULL, copie le niveau de la ligne parcours-lyceens la plus récente avec niveau IS NOT NULL.
UPDATE "users" u
SET "niveau" = pl."niveau"
FROM (
SELECT DISTINCT ON ("userId") "userId", "niveau"
FROM "parcours-lyceens"
WHERE "niveau" IS NOT NULL
ORDER BY "userId", "createdAt" DESC
) pl
WHERE u."id" = pl."userId"
AND u."niveau" IS NULL
Reason : backfill.
Étape 2 — pré-plateforme
Pour les lycéens encore sans niveau après l'étape 1, créés avant le 31/07/2023 : force Post-bac.
Ces utilisateurs existaient avant la mise en production de la feature niveau ; ils sont considérés comme post-bac par défaut.
Reason : backfill.
Mise à jour en temps réel (questionnaire)
À chaque soumission ou mise à jour de parcours-lyceens (via /formulaire ou mise à jour partielle), si le champ niveau est présent et valide, users.niveau est synchronisé immédiatement.
Reason : questionnaire.
Traçabilité
Chaque modification de users.niveau crée une ligne dans user-niveau-history :
| Colonne | Description |
|---|---|
userId |
ID de l'utilisateur |
oldNiveau |
Valeur avant (null si premier passage) |
newNiveau |
Valeur après |
reason |
Source : questionnaire, cron_aout, cron_decembre, cron_janvier, backfill, admin |
createdAt |
Horodatage |
Dashboard admin disponible sur /admin/niveau : répartition actuelle + historique des exécutions.
Interaction avec Dema1n - Passage Postbac — Flux cross-platform
Les lycéens MVLS sont inscrits sur deux plateformes : Inspire (niveau scolaire) et Dema1n (programme de mentorat). Quand un LY MVLS passe en Postbac sur Inspire, il doit quitter le programme MVLS côté Dema1n (HORS_PROGRAMME) car le programme ne couvre pas le post-bac.
Deux crons, deux comportements
| Date | Cron | Périmètre | Notif cross-platform |
|---|---|---|---|
| 01/12 | progressEnTransitionNonMvls |
Tous sauf MVLS (charte=true + MATCHE) | Aucune |
| 10/01 | progressEnTransitionMvls |
MVLS uniquement (charte=true + MATCHE) | USER_POSTBAC → Dema1n |
Le décalage de date (décembre vs janvier) laisse aux LY MVLS le temps de finaliser leur suivi avant d'être basculés.
Convention email mvls_
Sur Inspire, les comptes MVLS ont un username de la forme mvls_prenom@email.com. Le préfixe est strippé avant l'envoi RabbitMQ — Dema1n le ré-ajoute de son côté pour retrouver l'utilisateur (findByEmail('mvls_' + body.email)).
Ce que ça fait côté Dema1n
USER_POSTBAC reçu → trouve le jeune → changeStatusJeune(HORS_PROGRAMME). Les binômes actifs ne sont pas annulés automatiquement.
Débugger un LY MVLS non basculé
- Vérifier les logs Inspire :
✅ progressEnTransitionMvls : N lycéens MVLS passés en Post-bac - Si N > 0, vérifier les logs Dema1n :
[MVLS] Traitement USER_POSTBAC pour email: ... - Si absent, vérifier que RabbitMQ est up — exchange
dema1n, routing keyuser___mvls, queueDema1n_mvls - Si l'utilisateur n'est pas trouvé côté Dema1n, vérifier que son email Inspire correspond bien à
mvls_<email>en base Dema1n