sortants.fr
Les chances d’entrer dans un master, et ce que deviennent ceux qui en sortent. Deux jeux de données de l’État que personne ne croisait.
En bref
Le ministère publie d’un côté l’admission en master, de l’autre l’insertion des diplômés. Les deux jeux ne partagent aucune clé évidente, et l’un pèse un million de lignes. sortants.fr les croise une fois pour les 8 845 masters de la plateforme, et publie le résultat sans compte, sans publicité et sans palmarès.
- Nature
- Projet personnel, sans commanditaire
- État
- En ligne depuis septembre 2026, refait à chaque publication du ministère
- Rôle
- Tout : données, modèle, site, déploiement, rédaction
- Pile
- Python, DuckDB, SQL versionné, Parquet, GitHub Actions
- Volume
- 9 568 pages, 1 036 781 enregistrements d’insertion, 120 commits en 22 jours
- En ligne
- sortants.fr
Mots-clésPython · DuckDB · SQL · Parquet · open data · pipeline de données · qualité des données · contrôles bloquants · génération de site statique · GitHub Actions · CI/CD · déploiement FTPS · accessibilité · SVG · Content Security Policy · PHP · IndexNow


Ce que c’est

Une fiche par formation : candidatures, places, rang du dernier appelé, taux d’accès, puis l’emploi et le salaire à 6, 12, 18, 24 et 30 mois quand le ministère les publie. Chaque chiffre porte son dénominateur et remonte à son fichier source. Une valeur masquée par le secret statistique reste masquée : elle n’est ni estimée ni interpolée, et chaque fiche affiche son indice de complétude.


Comment ça marche
Tout est calculé avant la mise en ligne. Le seul code qui tourne en production est le formulaire de contact. La chaîne tourne à chaque modification, et s’arrête au premier contrôle qui échoue.
- Les donnéesMoissonjeux du ministère, horodatés, jamais retouchés
- Les contrôlesgardeContrat d’entréeempreinte, fraîcheur, colonnes attendues, pivots
- Les donnéesgardeConstructionDuckDB, SQL versionné, sept invariants
- Les contrôlesgardeComparaison au millésime précédentun écart se motive par écrit ou arrête tout
- Les contrôlesgardeRapport qualité datéseuils de couverture, code d’erreur si l’un tombe
- Le siteRendu de 9 568 pages30 secondes sur 16 cœurs, une fiche sur 200 revérifiée
- Les contrôlesgardeRecette du site produitune vingtaine de familles de contrôles sur le HTML
- Le siteDépôt incrémentiel8 connexions chiffrées, la configuration en dernier
- Les contrôlesgardeSonde en lignel’accueil doit répondreSans réponse : l’ancien fichier est remis.
42 sur 51exécutions réussies en intégration continue. Les six échecs sont chacun fermés par un commit qui dit lequel.
Ce que j’ai décidé
Un déposeur écrit à la main
- Retenu
- Un envoi incrémentiel : une empreinte par fichier, comparée à un relevé gardé sur le serveur, sur 8 connexions.
- Écarté
- Le miroir complet par l’outil habituel.
Le miroir renvoyait les 9 954 fichiers à chaque mise en ligne : 2 h 46 pour le premier dépôt, et le quota mensuel de la forge épuisé en deux passages. Depuis, un dépôt complet prend une vingtaine de minutes, les suivants une minute.
Un contrat d’entrée avant la construction
- Retenu
- Quatre contrôles sur les fichiers reçus, avant tout calcul.
- Écarté
- Se contenter des seuils de couverture mesurés en fin de chaîne.
Un seuil mesuré en aval laisse passer toute altération qui préserve les volumes, et le producteur avait déjà modifié la structure d’un jeu sans changer son adresse.
Une déclaration n’est pas une mesure
- Retenu
- Les métiers visés et les projets de recrutement paraissent sur les 325 pages de mention, au grain national.
- Écarté
- Descendre ces chiffres sur chaque fiche, au département.
Au département, 426 formations n’ont pas de valeur exploitable et un quart des couples passent sous 50 projets. Un chiffre juste sur trois fiches et absurde sur la quatrième ne se publie pas sur 8 845 pages.
Ce qui a raté
- Le site en erreur pendant 3 h 40. Une directive refusée par le serveur a mis toutes les pages en erreur 500. Depuis, le fichier de configuration part seul et en dernier, une sonde interroge le site, et l’ancien fichier est remis si elle échoue.
- Un graphique faux sur 3 270 fiches, cinq jours. L’axe était figé entre 60 et 100 %, et une formation à 25 % était dessinée à 60 %. La recette lisait le texte des pages, jamais la géométrie de leurs dessins. Elle vérifie maintenant que deux valeurs différentes ne sont jamais à la même hauteur.
- Un dénominateur mal dit sur 6 549 fiches. L’emploi stable était présenté comme une part de la promotion, alors qu’il se calcule sur les seuls salariés. Vérifié sur 439 935 observations avant correction. La règle qui en sort : les dénominateurs s’écrivent du plus large au plus étroit.
- Une donnée personnelle sur 422 pages. La fiche écrivait « admises : 1 sur 1 » sur des formations à un seul admis, ce qui dit le sexe d’une personne identifiable. Un seuil d’effectif de 20 s’applique désormais partout.
Un arbitrage en image






Outils
- Python 3.12, DuckDB, SQL versionné, Parquet suivi par Git LFS.
- JavaScript sans dépendance pour la recherche et le comparateur, chaque fichier disant ce qui marche sans lui.
- PHP pour le formulaire de contact, seul code exécuté en production.
- GitHub Actions avec actions épinglées par empreinte, FTP sur TLS, IndexNow.
- Polices sous-ensemblées par un script reproductible, graphiques SVG calculés au build.
Ce que ça prouve
Que je sais tenir seul une chaîne de données de la source à la mise en ligne, mettre les contrôles là où une erreur coûte cher, et corriger ce qui a été mal publié en écrivant ce qui avait cloché.