Skip to content

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 avec users.mvls = 'mvls' qui est posé dès le début du questionnaire — voir docs/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é

  1. Vérifier les logs Inspire : ✅ progressEnTransitionMvls : N lycéens MVLS passés en Post-bac
  2. Si N > 0, vérifier les logs Dema1n : [MVLS] Traitement USER_POSTBAC pour email: ...
  3. Si absent, vérifier que RabbitMQ est up — exchange dema1n, routing key user___mvls, queue Dema1n_mvls
  4. Si l'utilisateur n'est pas trouvé côté Dema1n, vérifier que son email Inspire correspond bien à mvls_<email> en base Dema1n