diff --git a/server/src/common/repositories/formationStats.js b/server/src/common/repositories/formationStats.js
index 0ea0a7a1..e969dc23 100644
--- a/server/src/common/repositories/formationStats.js
+++ b/server/src/common/repositories/formationStats.js
@@ -1,6 +1,7 @@
import { StatsRepository } from "./base.js";
import { dbCollection } from "#src/common/db/mongodb.js";
import { name } from "#src/common/db/collections/formationsStats.js";
+import { getMillesimeFormationsYearFrom } from "#src/common/stats.js";
export class FormationStatsRepository extends StatsRepository {
constructor() {
@@ -33,6 +34,44 @@ export class FormationStatsRepository extends StatsRepository {
return result ? result.map((data) => data.millesime) : [];
}
+
+ // Retourne les formations du supérieur possédant un millésime aggregé et un millésime unique
+ async findMillesimeInDouble(millesime, filiere = "superieur") {
+ const millesimeYear = getMillesimeFormationsYearFrom(millesime);
+
+ return await dbCollection(this.getCollection())
+ .aggregate([
+ { $match: { filiere, millesime: millesimeYear } },
+ {
+ $lookup: {
+ from: this.getCollection(),
+ localField: "uai",
+ foreignField: "uai",
+ as: "others",
+ let: { code_certification_base: "$code_certification" },
+ pipeline: [
+ {
+ $match: {
+ $expr: {
+ $and: [
+ { $eq: ["$filiere", filiere] },
+ { $eq: ["$$code_certification_base", "$code_certification"] },
+ { $eq: ["$millesime", millesime] },
+ ],
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ $match: {
+ "others.0": { $exists: true },
+ },
+ },
+ ])
+ .toArray();
+ }
}
export default new FormationStatsRepository();
diff --git a/server/src/common/stats.js b/server/src/common/stats.js
index feae78b7..965a94c4 100644
--- a/server/src/common/stats.js
+++ b/server/src/common/stats.js
@@ -84,6 +84,23 @@ export function getLastMillesimesFormationsSup() {
return config.millesimes.formationsSup[config.millesimes.formationsSup.length - 1];
}
+export function getLastMillesimesFormationsFor(filiere) {
+ return filiere === "superieur" ? getLastMillesimesFormationsSup() : getLastMillesimesFormations();
+}
+
+export function getLastMillesimesFormationsYearFor(filiere) {
+ const millesime = filiere === "superieur" ? getLastMillesimesFormationsSup() : getLastMillesimesFormations();
+ return millesime.split("_")[1];
+}
+
+export function getMillesimeFormationsFrom(millesime) {
+ return `${parseInt(millesime) - 1}_${millesime}`;
+}
+
+export function getMillesimeFormationsYearFrom(millesime) {
+ return millesime.split("_")[1];
+}
+
export function getMillesimesRegionales() {
return config.millesimes.regionales;
}
@@ -92,6 +109,10 @@ export function getLastMillesimesRegionales() {
return config.millesimes.regionales[config.millesimes.regionales.length - 1];
}
+export function isMillesimesYearSingle(millesime) {
+ return millesime.split("_").length === 1 ? true : false;
+}
+
function divide({ dividend, divisor }) {
return {
compute: (data) => percentage(data[dividend], data[divisor]),
diff --git a/server/src/http/routes/formationsRoutes.js b/server/src/http/routes/formationsRoutes.js
index 25973969..26b23a52 100644
--- a/server/src/http/routes/formationsRoutes.js
+++ b/server/src/http/routes/formationsRoutes.js
@@ -2,6 +2,7 @@ import express from "express";
import { tryCatch } from "#src/http/middlewares/tryCatchMiddleware.js";
import { authMiddleware } from "#src/http/middlewares/authMiddleware.js";
import Joi from "joi";
+import { flatten } from "lodash-es";
import * as validators from "#src/http/utils/validators.js";
import { validate } from "#src/http/utils/validators.js";
import { addCsvHeaders, addJsonHeaders, sendStats, sendImageOnError } from "#src/http/utils/responseUtils.js";
@@ -15,7 +16,11 @@ import { getStatsAsColumns } from "#src/common/utils/csvUtils.js";
import {
getLastMillesimesFormations,
getLastMillesimesFormationsSup,
+ getLastMillesimesFormationsYearFor,
+ getMillesimeFormationsFrom,
+ getMillesimeFormationsYearFrom,
transformDisplayStat,
+ isMillesimesYearSingle,
} from "#src/common/stats.js";
import BCNRepository from "#src/common/repositories/bcn.js";
import BCNSiseRepository from "#src/common/repositories/bcnSise.js";
@@ -36,7 +41,12 @@ async function formationStats({ uai, codeCertificationWithType, millesime }) {
const result = await FormationStatsRepository.first({
uai,
code_certification: code_certification,
- millesime: formatMillesime(millesime),
+ millesime: [
+ millesime,
+ isMillesimesYearSingle(millesime)
+ ? getMillesimeFormationsFrom(millesime)
+ : getMillesimeFormationsYearFrom(millesime),
+ ],
});
if (!result) {
@@ -99,12 +109,35 @@ export default () => {
...(millesimes.length === 0
? {
$or: [
- { filiere: "superieur", millesime: getLastMillesimesFormationsSup() },
- { filiere: { $ne: "superieur" }, millesime: getLastMillesimesFormations() },
+ {
+ filiere: "superieur",
+ millesime: {
+ $in: [
+ getMillesimeFormationsYearFrom(getLastMillesimesFormationsSup()),
+ getLastMillesimesFormationsSup(),
+ ],
+ },
+ },
+ {
+ filiere: { $ne: "superieur" },
+ millesime: {
+ $in: [
+ getMillesimeFormationsYearFrom(getLastMillesimesFormations()),
+ getLastMillesimesFormations(),
+ ],
+ },
+ },
],
}
: {
- millesime: millesimes,
+ millesime: flatten(
+ millesimes.map((m) => {
+ return [
+ m,
+ isMillesimesYearSingle(m) ? getMillesimeFormationsFrom(m) : getMillesimeFormationsYearFrom(m),
+ ];
+ })
+ ),
}),
},
{
@@ -166,10 +199,9 @@ export default () => {
}
);
const codeCertificationWithType = formatCodeCertificationWithType(code_certification);
- const millesime =
- millesimeBase ||
- (codeCertificationWithType.filiere === "superieur" && getLastMillesimesFormationsSup()) ||
- getLastMillesimesFormations();
+ const millesime = formatMillesime(
+ millesimeBase || getLastMillesimesFormationsYearFor(codeCertificationWithType.filiere)
+ );
return sendImageOnError(
async () => {
@@ -206,15 +238,14 @@ export default () => {
);
const codeCertificationWithType = formatCodeCertificationWithType(code_certification);
- const millesime =
- millesimeBase ||
- (codeCertificationWithType.filiere === "superieur" && getLastMillesimesFormationsSup()) ||
- getLastMillesimesFormations();
+ const millesime = formatMillesime(
+ millesimeBase || getLastMillesimesFormationsYearFor(codeCertificationWithType.filiere)
+ );
try {
const stats = await formationStats({ uai, codeCertificationWithType, millesime });
const etablissement = await AcceEtablissementRepository.first({ numero_uai: uai });
- const data = await formatDataWidget({ stats, millesime, etablissement });
+ const data = await formatDataWidget({ stats, etablissement });
const widget = await getUserWidget({
hash,
@@ -240,7 +271,7 @@ export default () => {
options,
data: {
error: err.name,
- millesimes: formatMillesime(millesime).split("_"),
+ millesimes: millesime.split("_"),
code_certification,
uai,
},
diff --git a/server/src/http/utils/widgetUtils.js b/server/src/http/utils/widgetUtils.js
index bc68734f..063ddddc 100644
--- a/server/src/http/utils/widgetUtils.js
+++ b/server/src/http/utils/widgetUtils.js
@@ -1,7 +1,7 @@
import { buildDescriptionFiliere, buildDescription } from "#src/common/stats.js";
import { formatMillesime } from "#src/http/utils/formatters.js";
-export async function formatDataWidget({ stats, millesime, region = null, etablissement = null }) {
+export async function formatDataWidget({ stats, region = null, etablissement = null }) {
const description = buildDescription(stats);
const data = {
@@ -12,7 +12,7 @@ export async function formatDataWidget({ stats, millesime, region = null, etabli
{ name: "emploi", value: stats.taux_en_emploi_6_mois },
{ name: "autres", value: stats.taux_autres_6_mois },
],
- millesimes: formatMillesime(millesime).split("_"),
+ millesimes: formatMillesime(stats.millesime).split("_"),
description,
// TODO: fix libelle BCN
formationLibelle: stats.libelle,
diff --git a/server/src/jobs/stats/computeUAI.js b/server/src/jobs/stats/computeUAI.js
index 3839ded2..917fed47 100644
--- a/server/src/jobs/stats/computeUAI.js
+++ b/server/src/jobs/stats/computeUAI.js
@@ -56,6 +56,18 @@ async function computeUAIBase(millesime, result, handleError) {
async (stats) => {
const { uai, code_formation_diplome, millesime, filiere } = stats;
+ // On ne connait pas le type de l'uai pour le supérieur
+ if (filiere === "superieur") {
+ return {
+ uai: stats.uai,
+ uai_type: "inconnu",
+ uai_donnee: stats.uai,
+ uai_donnee_type: "inconnu",
+ code_certification: stats.code_certification,
+ millesime: millesime,
+ };
+ }
+
// Le lieu de formation, le formateur et le gestionnaire sont identiques pour la voie scolaire
if (filiere !== "apprentissage") {
return {
diff --git a/server/src/jobs/stats/importFormationsSupStats.js b/server/src/jobs/stats/importFormationsSupStats.js
index 58b4362f..1cd576c0 100644
--- a/server/src/jobs/stats/importFormationsSupStats.js
+++ b/server/src/jobs/stats/importFormationsSupStats.js
@@ -9,6 +9,7 @@ import { findRegionByNom, findAcademieByNom } from "#src/services/regions.js";
import { computeCustomStats, getMillesimesFormationsSup, INSERSUP_STATS_NAMES } from "#src/common/stats.js";
import { getCertificationSupInfo } from "#src/common/certification.js";
import { InserSup } from "#src/services/dataEnseignementSup/InserSup.js";
+import FormationStatsRepository from "#src/common/repositories/formationStats.js";
const logger = getLoggerWithContext("import");
@@ -23,6 +24,21 @@ function formatStats(stats) {
};
}
+async function checkMillesimeInDouble(jobStats, millesimes) {
+ for (const millesime of millesimes) {
+ const results = await FormationStatsRepository.findMillesimeInDouble(millesime);
+ for (const result of results) {
+ jobStats.failed++;
+ const formatted = {
+ ...pick(result, ["uai", "code_certification", "filiere"]),
+ millesimes: [result.millesime, ...result.others.map((r) => r.millesime)],
+ };
+ logger.error(`Millésime en double pour : `, formatted);
+ jobStats.error = `Millésime en double pour : ${JSON.stringify(formatted)}`;
+ }
+ }
+}
+
export async function importFormationsSupStats(options = {}) {
const jobStats = { created: 0, updated: 0, failed: 0 };
@@ -143,5 +159,10 @@ export async function importFormationsSupStats(options = {}) {
)
);
+ // Vérifie que l'on a pas un mélange millésime unique/aggregé pour une même année/formation
+ // Actuellement le cas n'existe pas, on met une alerte au cas ou
+ // Si le cas apparait : modifier les routes bulks pour envoyer l'information suivant les règles de priorités des millésimes
+ await checkMillesimeInDouble(jobStats, millesimes);
+
return jobStats;
}
diff --git a/server/src/services/dataEnseignementSup/InserSup.js b/server/src/services/dataEnseignementSup/InserSup.js
index 302a434f..e16ccda1 100644
--- a/server/src/services/dataEnseignementSup/InserSup.js
+++ b/server/src/services/dataEnseignementSup/InserSup.js
@@ -57,6 +57,7 @@ class InserSup {
const statsByMillesime = stats.reduce((acc, stat) => {
acc[stat.promo.join("_")] = acc[stat.promo.join("_")] ?? {
...stat,
+ millesime: stat.promo.join("_"),
nb_diplomes: stat.nb_sortants + stat.nb_poursuivants,
nb_en_emploi: {},
};
@@ -72,55 +73,20 @@ class InserSup {
}),
// Aggregate two millesimes
transformData((stats) => {
- // When the stats is already on two millesimes
- if (stats[millesime]) {
- return stats[millesime];
- }
- // We don't want to aggregate when stats are only available by millesimes for now
- return null;
-
- // const statsMerged = Object.values(stats).reduce((acc, stat) => {
- // if (!acc) {
- // return {
- // ...stat,
- // nb_en_emploi: mapValues(stat.nb_en_emploi, (v) => [v]),
- // };
- // }
-
- // return {
- // ...acc,
- // promo: [...acc.promo, ...stat.promo],
- // nb_poursuivants: acc.nb_poursuivants + stat.nb_poursuivants,
- // nb_sortants: acc.nb_sortants + stat.nb_sortants,
- // nb_diplomes: acc.nb_diplomes + stat.nb_diplomes,
- // nb_en_emploi: mergeWith(
- // acc.nb_en_emploi,
- // mapValues(stat.nb_en_emploi, (v) => [v]),
- // (objValue, srcValue) => objValue.concat(srcValue)
- // ),
- // };
- // }, null);
-
- // if (statsMerged.promo.length !== 2) {
- // return null;
- // }
-
- // // Remove value that not exist on both millesime
- // statsMerged.nb_en_emploi = mapValues(statsMerged.nb_en_emploi, (v) => {
- // if (v.length !== 2 || v.some((v) => v === null)) {
- // return null;
- // }
- // return v.reduce((s, v) => s + v, 0);
- // });
-
- // return statsMerged;
+ return Object.values(stats).filter((stats) => {
+ return (
+ stats.millesime === millesime ||
+ stats.millesime === millesimePart[0] ||
+ stats.millesime === millesimePart[1]
+ );
+ });
}),
+ flattenArray(),
// Format data
transformData((stats) => {
return {
...stats,
...stats.nb_en_emploi,
- millesime,
};
}),
writeData((stats) => {
diff --git a/server/tests/fixtures/files/inserSup/formationsMillesimesMixtes.json b/server/tests/fixtures/files/inserSup/formationsMillesimesMixtes.json
new file mode 100644
index 00000000..cc6b7a65
--- /dev/null
+++ b/server/tests/fixtures/files/inserSup/formationsMillesimesMixtes.json
@@ -0,0 +1,250 @@
+[
+ {
+ "date_jeu": "2023_S2",
+ "reg_nom": "Provence-Alpes-Côte d'Azur",
+ "aca_nom": "Nice",
+ "uo_lib": "Université Côte d'Azur",
+ "uo_lib_actuel": "Université Côte d'Azur",
+ "type_diplome_long": "Master LMD",
+ "dom_lib": "Sciences, technologies, santé",
+ "discipli_lib": "STAPS",
+ "sectdis_lib": "STAPS",
+ "libelle_diplome": "STAPS:ENTRAINEMENT ET OPTIMISATION DE LA PERFORMANCE SPORTIVE",
+ "source": "insersup",
+ "nb_poursuivants": "8",
+ "nb_sortants": "22",
+ "promo": [
+ "2020",
+ "2021"
+ ],
+ "date_inser_long": "18 mois après le diplôme",
+ "flag": "*",
+ "exception": "Moins de 20 sortants donc cumul de 2 promotions",
+ "taux_emploi_sal_fr": "59.1",
+ "taux_insertion": "-",
+ "taux_emploi": "-",
+ "reg_id": "R93",
+ "aca_id": "A23",
+ "id_paysage": "s3t8T",
+ "id_paysage_actuel": "s3t8T",
+ "etablissement": "0062205P",
+ "type_diplome": "master_LMD",
+ "dom": "STS",
+ "discipli": "10",
+ "sectdis": "10",
+ "diplome": "2500200",
+ "date_inser": 18
+ },
+ {
+ "date_jeu": "2023_S2",
+ "reg_nom": "Provence-Alpes-Côte d'Azur",
+ "aca_nom": "Nice",
+ "uo_lib": "Université Côte d'Azur",
+ "uo_lib_actuel": "Université Côte d'Azur",
+ "type_diplome_long": "Master LMD",
+ "dom_lib": "Sciences, technologies, santé",
+ "discipli_lib": "STAPS",
+ "sectdis_lib": "STAPS",
+ "libelle_diplome": "STAPS:ENTRAINEMENT ET OPTIMISATION DE LA PERFORMANCE SPORTIVE",
+ "source": "insersup",
+ "nb_poursuivants": "8",
+ "nb_sortants": "22",
+ "promo": [
+ "2020",
+ "2021"
+ ],
+ "date_inser_long": "6 mois après le diplôme",
+ "flag": "*",
+ "exception": "Moins de 20 sortants donc cumul de 2 promotions",
+ "taux_emploi_sal_fr": "50.0",
+ "taux_insertion": "-",
+ "taux_emploi": "-",
+ "reg_id": "R93",
+ "aca_id": "A23",
+ "id_paysage": "s3t8T",
+ "id_paysage_actuel": "s3t8T",
+ "etablissement": "0062205P",
+ "type_diplome": "master_LMD",
+ "dom": "STS",
+ "discipli": "10",
+ "sectdis": "10",
+ "diplome": "2500200",
+ "date_inser": 6
+ },
+ {
+ "date_jeu": "2023_S2",
+ "reg_nom": "Provence-Alpes-Côte d'Azur",
+ "aca_nom": "Nice",
+ "uo_lib": "Université Côte d'Azur",
+ "uo_lib_actuel": "Université Côte d'Azur",
+ "type_diplome_long": "Master LMD",
+ "dom_lib": "Sciences, technologies, santé",
+ "discipli_lib": "STAPS",
+ "sectdis_lib": "STAPS",
+ "libelle_diplome": "STAPS:ENTRAINEMENT ET OPTIMISATION DE LA PERFORMANCE SPORTIVE",
+ "source": "insersup",
+ "nb_poursuivants": "8",
+ "nb_sortants": "22",
+ "promo": [
+ "2020",
+ "2021"
+ ],
+ "date_inser_long": "12 mois après le diplôme",
+ "flag": "*",
+ "exception": "Moins de 20 sortants donc cumul de 2 promotions",
+ "taux_emploi_sal_fr": "63.6",
+ "taux_insertion": "-",
+ "taux_emploi": "-",
+ "reg_id": "R93",
+ "aca_id": "A23",
+ "id_paysage": "s3t8T",
+ "id_paysage_actuel": "s3t8T",
+ "etablissement": "0062205P",
+ "type_diplome": "master_LMD",
+ "dom": "STS",
+ "discipli": "10",
+ "sectdis": "10",
+ "diplome": "2500200",
+ "date_inser": 12
+ },
+ {
+ "date_jeu": "2023_S2",
+ "reg_nom": "Provence-Alpes-Côte d'Azur",
+ "aca_nom": "Nice",
+ "uo_lib": "Université Côte d'Azur",
+ "uo_lib_actuel": "Université Côte d'Azur",
+ "type_diplome_long": "Master MEEF",
+ "dom_lib": "Sciences humaines et sociales",
+ "discipli_lib": "Sciences humaines et sociales",
+ "sectdis_lib": "Sciences de l'éducation",
+ "libelle_diplome": "METIERS DE L'ENSEIGNEMENT, DE L'EDUCATION ET DE LA FORMATION (MEEF), 2e DEGRE",
+ "source": "insersup",
+ "nb_poursuivants": "24",
+ "nb_sortants": "117",
+ "promo": [
+ "2020"
+ ],
+ "date_inser_long": "6 mois après le diplôme",
+ "flag": null,
+ "exception": null,
+ "taux_emploi_sal_fr": "69.2",
+ "taux_insertion": "-",
+ "taux_emploi": "-",
+ "reg_id": "R93",
+ "aca_id": "A23",
+ "id_paysage": "s3t8T",
+ "id_paysage_actuel": "s3t8T",
+ "etablissement": "0062205P",
+ "type_diplome": "master_MEEF",
+ "dom": "SHS",
+ "discipli": "06",
+ "sectdis": "34",
+ "diplome": "2500200",
+ "date_inser": 6
+ },
+ {
+ "date_jeu": "2023_S2",
+ "reg_nom": "Provence-Alpes-Côte d'Azur",
+ "aca_nom": "Nice",
+ "uo_lib": "Université Côte d'Azur",
+ "uo_lib_actuel": "Université Côte d'Azur",
+ "type_diplome_long": "Master MEEF",
+ "dom_lib": "Sciences humaines et sociales",
+ "discipli_lib": "Sciences humaines et sociales",
+ "sectdis_lib": "Sciences de l'éducation",
+ "libelle_diplome": "METIERS DE L'ENSEIGNEMENT, DE L'EDUCATION ET DE LA FORMATION (MEEF), 2e DEGRE",
+ "source": "insersup",
+ "nb_poursuivants": "24",
+ "nb_sortants": "117",
+ "promo": [
+ "2020"
+ ],
+ "date_inser_long": "18 mois après le diplôme",
+ "flag": null,
+ "exception": null,
+ "taux_emploi_sal_fr": "85.5",
+ "taux_insertion": "-",
+ "taux_emploi": "-",
+ "reg_id": "R93",
+ "aca_id": "A23",
+ "id_paysage": "s3t8T",
+ "id_paysage_actuel": "s3t8T",
+ "etablissement": "0062205P",
+ "type_diplome": "master_MEEF",
+ "dom": "SHS",
+ "discipli": "06",
+ "sectdis": "34",
+ "diplome": "2500200",
+ "date_inser": 18
+ },
+ {
+ "date_jeu": "2023_S2",
+ "reg_nom": "Provence-Alpes-Côte d'Azur",
+ "aca_nom": "Nice",
+ "uo_lib": "Université Côte d'Azur",
+ "uo_lib_actuel": "Université Côte d'Azur",
+ "type_diplome_long": "Master MEEF",
+ "dom_lib": "Sciences humaines et sociales",
+ "discipli_lib": "Sciences humaines et sociales",
+ "sectdis_lib": "Sciences de l'éducation",
+ "libelle_diplome": "METIERS DE L'ENSEIGNEMENT, DE L'EDUCATION ET DE LA FORMATION (MEEF), 2e DEGRE",
+ "source": "insersup",
+ "nb_poursuivants": "24",
+ "nb_sortants": "117",
+ "promo": [
+ "2020"
+ ],
+ "date_inser_long": "24 mois après le diplôme",
+ "flag": null,
+ "exception": null,
+ "taux_emploi_sal_fr": "88.0",
+ "taux_insertion": "-",
+ "taux_emploi": "-",
+ "reg_id": "R93",
+ "aca_id": "A23",
+ "id_paysage": "s3t8T",
+ "id_paysage_actuel": "s3t8T",
+ "etablissement": "0062205P",
+ "type_diplome": "master_MEEF",
+ "dom": "SHS",
+ "discipli": "06",
+ "sectdis": "34",
+ "diplome": "2500200",
+ "date_inser": 24
+ },
+ {
+ "date_jeu": "2023_S2",
+ "reg_nom": "Provence-Alpes-Côte d'Azur",
+ "aca_nom": "Nice",
+ "uo_lib": "Université Côte d'Azur",
+ "uo_lib_actuel": "Université Côte d'Azur",
+ "type_diplome_long": "Master MEEF",
+ "dom_lib": "Sciences humaines et sociales",
+ "discipli_lib": "Sciences humaines et sociales",
+ "sectdis_lib": "Sciences de l'éducation",
+ "libelle_diplome": "METIERS DE L'ENSEIGNEMENT, DE L'EDUCATION ET DE LA FORMATION (MEEF), 2e DEGRE",
+ "source": "insersup",
+ "nb_poursuivants": "24",
+ "nb_sortants": "117",
+ "promo": [
+ "2020"
+ ],
+ "date_inser_long": "12 mois après le diplôme",
+ "flag": null,
+ "exception": null,
+ "taux_emploi_sal_fr": "71.8",
+ "taux_insertion": "-",
+ "taux_emploi": "-",
+ "reg_id": "R93",
+ "aca_id": "A23",
+ "id_paysage": "s3t8T",
+ "id_paysage_actuel": "s3t8T",
+ "etablissement": "0062205P",
+ "type_diplome": "master_MEEF",
+ "dom": "SHS",
+ "discipli": "06",
+ "sectdis": "34",
+ "diplome": "2500200",
+ "date_inser": 12
+ }
+]
\ No newline at end of file
diff --git a/server/tests/fixtures/files/inserSup/formationsMillesimesMixtesWithDouble.json b/server/tests/fixtures/files/inserSup/formationsMillesimesMixtesWithDouble.json
new file mode 100644
index 00000000..850e2960
--- /dev/null
+++ b/server/tests/fixtures/files/inserSup/formationsMillesimesMixtesWithDouble.json
@@ -0,0 +1,355 @@
+[
+ {
+ "date_jeu": "2023_S2",
+ "reg_nom": "Provence-Alpes-Côte d'Azur",
+ "aca_nom": "Nice",
+ "uo_lib": "Université Côte d'Azur",
+ "uo_lib_actuel": "Université Côte d'Azur",
+ "type_diplome_long": "Master LMD",
+ "dom_lib": "Sciences, technologies, santé",
+ "discipli_lib": "STAPS",
+ "sectdis_lib": "STAPS",
+ "libelle_diplome": "STAPS:ENTRAINEMENT ET OPTIMISATION DE LA PERFORMANCE SPORTIVE",
+ "source": "insersup",
+ "nb_poursuivants": "8",
+ "nb_sortants": "22",
+ "promo": [
+ "2020",
+ "2021"
+ ],
+ "date_inser_long": "18 mois après le diplôme",
+ "flag": "*",
+ "exception": "Moins de 20 sortants donc cumul de 2 promotions",
+ "taux_emploi_sal_fr": "59.1",
+ "taux_insertion": "-",
+ "taux_emploi": "-",
+ "reg_id": "R93",
+ "aca_id": "A23",
+ "id_paysage": "s3t8T",
+ "id_paysage_actuel": "s3t8T",
+ "etablissement": "0062205P",
+ "type_diplome": "master_LMD",
+ "dom": "STS",
+ "discipli": "10",
+ "sectdis": "10",
+ "diplome": "2500200",
+ "date_inser": 18
+ },
+ {
+ "date_jeu": "2023_S2",
+ "reg_nom": "Provence-Alpes-Côte d'Azur",
+ "aca_nom": "Nice",
+ "uo_lib": "Université Côte d'Azur",
+ "uo_lib_actuel": "Université Côte d'Azur",
+ "type_diplome_long": "Master LMD",
+ "dom_lib": "Sciences, technologies, santé",
+ "discipli_lib": "STAPS",
+ "sectdis_lib": "STAPS",
+ "libelle_diplome": "STAPS:ENTRAINEMENT ET OPTIMISATION DE LA PERFORMANCE SPORTIVE",
+ "source": "insersup",
+ "nb_poursuivants": "8",
+ "nb_sortants": "22",
+ "promo": [
+ "2020",
+ "2021"
+ ],
+ "date_inser_long": "6 mois après le diplôme",
+ "flag": "*",
+ "exception": "Moins de 20 sortants donc cumul de 2 promotions",
+ "taux_emploi_sal_fr": "50.0",
+ "taux_insertion": "-",
+ "taux_emploi": "-",
+ "reg_id": "R93",
+ "aca_id": "A23",
+ "id_paysage": "s3t8T",
+ "id_paysage_actuel": "s3t8T",
+ "etablissement": "0062205P",
+ "type_diplome": "master_LMD",
+ "dom": "STS",
+ "discipli": "10",
+ "sectdis": "10",
+ "diplome": "2500200",
+ "date_inser": 6
+ },
+ {
+ "date_jeu": "2023_S2",
+ "reg_nom": "Provence-Alpes-Côte d'Azur",
+ "aca_nom": "Nice",
+ "uo_lib": "Université Côte d'Azur",
+ "uo_lib_actuel": "Université Côte d'Azur",
+ "type_diplome_long": "Master LMD",
+ "dom_lib": "Sciences, technologies, santé",
+ "discipli_lib": "STAPS",
+ "sectdis_lib": "STAPS",
+ "libelle_diplome": "STAPS:ENTRAINEMENT ET OPTIMISATION DE LA PERFORMANCE SPORTIVE",
+ "source": "insersup",
+ "nb_poursuivants": "8",
+ "nb_sortants": "22",
+ "promo": [
+ "2020",
+ "2021"
+ ],
+ "date_inser_long": "12 mois après le diplôme",
+ "flag": "*",
+ "exception": "Moins de 20 sortants donc cumul de 2 promotions",
+ "taux_emploi_sal_fr": "63.6",
+ "taux_insertion": "-",
+ "taux_emploi": "-",
+ "reg_id": "R93",
+ "aca_id": "A23",
+ "id_paysage": "s3t8T",
+ "id_paysage_actuel": "s3t8T",
+ "etablissement": "0062205P",
+ "type_diplome": "master_LMD",
+ "dom": "STS",
+ "discipli": "10",
+ "sectdis": "10",
+ "diplome": "2500200",
+ "date_inser": 12
+ },
+ {
+ "date_jeu": "2023_S2",
+ "reg_nom": "Provence-Alpes-Côte d'Azur",
+ "aca_nom": "Nice",
+ "uo_lib": "Université Côte d'Azur",
+ "uo_lib_actuel": "Université Côte d'Azur",
+ "type_diplome_long": "Master MEEF",
+ "dom_lib": "Sciences humaines et sociales",
+ "discipli_lib": "Sciences humaines et sociales",
+ "sectdis_lib": "Sciences de l'éducation",
+ "libelle_diplome": "METIERS DE L'ENSEIGNEMENT, DE L'EDUCATION ET DE LA FORMATION (MEEF), 2e DEGRE",
+ "source": "insersup",
+ "nb_poursuivants": "11",
+ "nb_sortants": "120",
+ "promo": [
+ "2021"
+ ],
+ "date_inser_long": "12 mois après le diplôme",
+ "flag": null,
+ "exception": null,
+ "taux_emploi_sal_fr": "92.5",
+ "taux_insertion": "-",
+ "taux_emploi": "-",
+ "reg_id": "R93",
+ "aca_id": "A23",
+ "id_paysage": "s3t8T",
+ "id_paysage_actuel": "s3t8T",
+ "etablissement": "0062205P",
+ "type_diplome": "master_MEEF",
+ "dom": "SHS",
+ "discipli": "06",
+ "sectdis": "34",
+ "diplome": "2500200",
+ "date_inser": 12
+ },
+ {
+ "date_jeu": "2023_S2",
+ "reg_nom": "Provence-Alpes-Côte d'Azur",
+ "aca_nom": "Nice",
+ "uo_lib": "Université Côte d'Azur",
+ "uo_lib_actuel": "Université Côte d'Azur",
+ "type_diplome_long": "Master MEEF",
+ "dom_lib": "Sciences humaines et sociales",
+ "discipli_lib": "Sciences humaines et sociales",
+ "sectdis_lib": "Sciences de l'éducation",
+ "libelle_diplome": "METIERS DE L'ENSEIGNEMENT, DE L'EDUCATION ET DE LA FORMATION (MEEF), 2e DEGRE",
+ "source": "insersup",
+ "nb_poursuivants": "24",
+ "nb_sortants": "117",
+ "promo": [
+ "2020"
+ ],
+ "date_inser_long": "6 mois après le diplôme",
+ "flag": null,
+ "exception": null,
+ "taux_emploi_sal_fr": "69.2",
+ "taux_insertion": "-",
+ "taux_emploi": "-",
+ "reg_id": "R93",
+ "aca_id": "A23",
+ "id_paysage": "s3t8T",
+ "id_paysage_actuel": "s3t8T",
+ "etablissement": "0062205P",
+ "type_diplome": "master_MEEF",
+ "dom": "SHS",
+ "discipli": "06",
+ "sectdis": "34",
+ "diplome": "2500200",
+ "date_inser": 6
+ },
+ {
+ "date_jeu": "2023_S2",
+ "reg_nom": "Provence-Alpes-Côte d'Azur",
+ "aca_nom": "Nice",
+ "uo_lib": "Université Côte d'Azur",
+ "uo_lib_actuel": "Université Côte d'Azur",
+ "type_diplome_long": "Master MEEF",
+ "dom_lib": "Sciences humaines et sociales",
+ "discipli_lib": "Sciences humaines et sociales",
+ "sectdis_lib": "Sciences de l'éducation",
+ "libelle_diplome": "METIERS DE L'ENSEIGNEMENT, DE L'EDUCATION ET DE LA FORMATION (MEEF), 2e DEGRE",
+ "source": "insersup",
+ "nb_poursuivants": "24",
+ "nb_sortants": "117",
+ "promo": [
+ "2020"
+ ],
+ "date_inser_long": "18 mois après le diplôme",
+ "flag": null,
+ "exception": null,
+ "taux_emploi_sal_fr": "85.5",
+ "taux_insertion": "-",
+ "taux_emploi": "-",
+ "reg_id": "R93",
+ "aca_id": "A23",
+ "id_paysage": "s3t8T",
+ "id_paysage_actuel": "s3t8T",
+ "etablissement": "0062205P",
+ "type_diplome": "master_MEEF",
+ "dom": "SHS",
+ "discipli": "06",
+ "sectdis": "34",
+ "diplome": "2500200",
+ "date_inser": 18
+ },
+ {
+ "date_jeu": "2023_S2",
+ "reg_nom": "Provence-Alpes-Côte d'Azur",
+ "aca_nom": "Nice",
+ "uo_lib": "Université Côte d'Azur",
+ "uo_lib_actuel": "Université Côte d'Azur",
+ "type_diplome_long": "Master MEEF",
+ "dom_lib": "Sciences humaines et sociales",
+ "discipli_lib": "Sciences humaines et sociales",
+ "sectdis_lib": "Sciences de l'éducation",
+ "libelle_diplome": "METIERS DE L'ENSEIGNEMENT, DE L'EDUCATION ET DE LA FORMATION (MEEF), 2e DEGRE",
+ "source": "insersup",
+ "nb_poursuivants": "24",
+ "nb_sortants": "117",
+ "promo": [
+ "2020"
+ ],
+ "date_inser_long": "24 mois après le diplôme",
+ "flag": null,
+ "exception": null,
+ "taux_emploi_sal_fr": "88.0",
+ "taux_insertion": "-",
+ "taux_emploi": "-",
+ "reg_id": "R93",
+ "aca_id": "A23",
+ "id_paysage": "s3t8T",
+ "id_paysage_actuel": "s3t8T",
+ "etablissement": "0062205P",
+ "type_diplome": "master_MEEF",
+ "dom": "SHS",
+ "discipli": "06",
+ "sectdis": "34",
+ "diplome": "2500200",
+ "date_inser": 24
+ },
+ {
+ "date_jeu": "2023_S2",
+ "reg_nom": "Provence-Alpes-Côte d'Azur",
+ "aca_nom": "Nice",
+ "uo_lib": "Université Côte d'Azur",
+ "uo_lib_actuel": "Université Côte d'Azur",
+ "type_diplome_long": "Master MEEF",
+ "dom_lib": "Sciences humaines et sociales",
+ "discipli_lib": "Sciences humaines et sociales",
+ "sectdis_lib": "Sciences de l'éducation",
+ "libelle_diplome": "METIERS DE L'ENSEIGNEMENT, DE L'EDUCATION ET DE LA FORMATION (MEEF), 2e DEGRE",
+ "source": "insersup",
+ "nb_poursuivants": "11",
+ "nb_sortants": "120",
+ "promo": [
+ "2021"
+ ],
+ "date_inser_long": "18 mois après le diplôme",
+ "flag": null,
+ "exception": null,
+ "taux_emploi_sal_fr": "92.5",
+ "taux_insertion": "-",
+ "taux_emploi": "-",
+ "reg_id": "R93",
+ "aca_id": "A23",
+ "id_paysage": "s3t8T",
+ "id_paysage_actuel": "s3t8T",
+ "etablissement": "0062205P",
+ "type_diplome": "master_MEEF",
+ "dom": "SHS",
+ "discipli": "06",
+ "sectdis": "34",
+ "diplome": "2500200",
+ "date_inser": 18
+ },
+ {
+ "date_jeu": "2023_S2",
+ "reg_nom": "Provence-Alpes-Côte d'Azur",
+ "aca_nom": "Nice",
+ "uo_lib": "Université Côte d'Azur",
+ "uo_lib_actuel": "Université Côte d'Azur",
+ "type_diplome_long": "Master MEEF",
+ "dom_lib": "Sciences humaines et sociales",
+ "discipli_lib": "Sciences humaines et sociales",
+ "sectdis_lib": "Sciences de l'éducation",
+ "libelle_diplome": "METIERS DE L'ENSEIGNEMENT, DE L'EDUCATION ET DE LA FORMATION (MEEF), 2e DEGRE",
+ "source": "insersup",
+ "nb_poursuivants": "11",
+ "nb_sortants": "120",
+ "promo": [
+ "2021"
+ ],
+ "date_inser_long": "6 mois après le diplôme",
+ "flag": null,
+ "exception": null,
+ "taux_emploi_sal_fr": "89.2",
+ "taux_insertion": "-",
+ "taux_emploi": "-",
+ "reg_id": "R93",
+ "aca_id": "A23",
+ "id_paysage": "s3t8T",
+ "id_paysage_actuel": "s3t8T",
+ "etablissement": "0062205P",
+ "type_diplome": "master_MEEF",
+ "dom": "SHS",
+ "discipli": "06",
+ "sectdis": "34",
+ "diplome": "2500200",
+ "date_inser": 6
+ },
+ {
+ "date_jeu": "2023_S2",
+ "reg_nom": "Provence-Alpes-Côte d'Azur",
+ "aca_nom": "Nice",
+ "uo_lib": "Université Côte d'Azur",
+ "uo_lib_actuel": "Université Côte d'Azur",
+ "type_diplome_long": "Master MEEF",
+ "dom_lib": "Sciences humaines et sociales",
+ "discipli_lib": "Sciences humaines et sociales",
+ "sectdis_lib": "Sciences de l'éducation",
+ "libelle_diplome": "METIERS DE L'ENSEIGNEMENT, DE L'EDUCATION ET DE LA FORMATION (MEEF), 2e DEGRE",
+ "source": "insersup",
+ "nb_poursuivants": "24",
+ "nb_sortants": "117",
+ "promo": [
+ "2020"
+ ],
+ "date_inser_long": "12 mois après le diplôme",
+ "flag": null,
+ "exception": null,
+ "taux_emploi_sal_fr": "71.8",
+ "taux_insertion": "-",
+ "taux_emploi": "-",
+ "reg_id": "R93",
+ "aca_id": "A23",
+ "id_paysage": "s3t8T",
+ "id_paysage_actuel": "s3t8T",
+ "etablissement": "0062205P",
+ "type_diplome": "master_MEEF",
+ "dom": "SHS",
+ "discipli": "06",
+ "sectdis": "34",
+ "diplome": "2500200",
+ "date_inser": 12
+ }
+]
\ No newline at end of file
diff --git a/server/tests/fixtures/widgets/dsfr/formations/0751234J-10221058_2019.svg b/server/tests/fixtures/widgets/dsfr/formations/0751234J-10221058_2019.svg
new file mode 100644
index 00000000..3cd35544
--- /dev/null
+++ b/server/tests/fixtures/widgets/dsfr/formations/0751234J-10221058_2019.svg
@@ -0,0 +1,208 @@
+
+
\ No newline at end of file
diff --git a/server/tests/fixtures/widgets/lba/formations/0751234J-10221058_2019.svg b/server/tests/fixtures/widgets/lba/formations/0751234J-10221058_2019.svg
new file mode 100644
index 00000000..f2f94e8e
--- /dev/null
+++ b/server/tests/fixtures/widgets/lba/formations/0751234J-10221058_2019.svg
@@ -0,0 +1,153 @@
+
+
+
+
+ Certification 10221058, établissement 0751234J
+
+
+ Données InserJeunes pour la certification 10221058 (BAC filière apprentissage) dispensée par l'établissement 0751234J, pour le millésime 2019
+
+
+
+
+
+
+
+
+ Les chiffres pour cet établissement
+
+
+
+
+
+
+
+
+
+
+ 25%
+
+
+ sont inscrits en formation
+
+
+ (Formation supérieure,redoublants,
+ changement de filière)
+
+
+
+
+
+
+
+
+ 50%
+
+
+ sont en emploi au bout de 6 mois
+
+
+ (tout type d'emploi salarié du privé)
+
+
+
+
+
+
+
+
+ 12%
+
+
+ sont dans d’autres cas
+
+
+ (Recherche d’emploi, service
+ civique, à l’étranger, statut
+ indépendant, etc.)
+
+
+
+
+
+
+
+ *Données issues du dispositif InserJeunes
+
+
+
+ promotion 2019
+
+
+
+
+
\ No newline at end of file
diff --git a/server/tests/http/formationsRoutes-test.js b/server/tests/http/formationsRoutes-test.js
index 16566d0d..01f1068d 100644
--- a/server/tests/http/formationsRoutes-test.js
+++ b/server/tests/http/formationsRoutes-test.js
@@ -103,6 +103,115 @@ describe("formationsRoutes", () => {
});
});
+ it("Vérifie qu'on peut obtenir les stats d'une formation avec a un millésime unique", async () => {
+ const { httpClient } = await startServer();
+ await insertFormationsStats({
+ uai: "0751234J",
+ code_certification: "12345678",
+ code_formation_diplome: "12345678",
+ millesime: "2018",
+ filiere: "apprentissage",
+ nb_annee_term: 100,
+ nb_poursuite_etudes: 1,
+ nb_en_emploi_24_mois: 2,
+ nb_en_emploi_18_mois: 3,
+ nb_en_emploi_12_mois: 4,
+ nb_en_emploi_6_mois: 5,
+ nb_sortant: 6,
+ taux_rupture_contrats: 7,
+ taux_en_formation: 8,
+ taux_en_emploi_24_mois: 9,
+ taux_en_emploi_18_mois: 10,
+ taux_en_emploi_12_mois: 11,
+ taux_en_emploi_6_mois: 12,
+ taux_autres_6_mois: 13,
+ taux_autres_12_mois: 14,
+ taux_autres_18_mois: 15,
+ taux_autres_24_mois: 16,
+ });
+
+ await insertFormationsStats({
+ uai: "0751234J",
+ code_certification: "12345678",
+ code_formation_diplome: "12345678",
+ millesime: "2019",
+ filiere: "apprentissage",
+ nb_annee_term: 100,
+ nb_poursuite_etudes: 1,
+ nb_en_emploi_24_mois: 2,
+ nb_en_emploi_18_mois: 3,
+ nb_en_emploi_12_mois: 4,
+ nb_en_emploi_6_mois: 5,
+ nb_sortant: 6,
+ taux_rupture_contrats: 7,
+ taux_en_formation: 8,
+ taux_en_emploi_24_mois: 9,
+ taux_en_emploi_18_mois: 10,
+ taux_en_emploi_12_mois: 11,
+ taux_en_emploi_6_mois: 12,
+ taux_autres_6_mois: 13,
+ taux_autres_12_mois: 14,
+ taux_autres_18_mois: 15,
+ taux_autres_24_mois: 16,
+ });
+
+ const response = await httpClient.get(`/api/inserjeunes/formations`, {
+ headers: {
+ ...getAuthHeaders(),
+ },
+ });
+
+ assert.strictEqual(response.status, 200);
+ assert.deepStrictEqual(response.data, {
+ formations: [
+ {
+ uai: "0751234J",
+ libelle_etablissement: "Lycée",
+ code_certification: "12345678",
+ code_certification_type: "cfd",
+ code_formation_diplome: "12345678",
+ libelle: "LIBELLE",
+ millesime: "2019",
+ filiere: "apprentissage",
+ diplome: { code: "4", libelle: "BAC" },
+ nb_annee_term: 100,
+ nb_poursuite_etudes: 1,
+ nb_en_emploi_24_mois: 2,
+ nb_en_emploi_18_mois: 3,
+ nb_en_emploi_12_mois: 4,
+ nb_en_emploi_6_mois: 5,
+ nb_sortant: 6,
+ taux_rupture_contrats: 7,
+ taux_en_formation: 8,
+ taux_en_emploi_24_mois: 9,
+ taux_en_emploi_18_mois: 10,
+ taux_en_emploi_12_mois: 11,
+ taux_en_emploi_6_mois: 12,
+ taux_autres_6_mois: 13,
+ taux_autres_12_mois: 14,
+ taux_autres_18_mois: 15,
+ taux_autres_24_mois: 16,
+ formation_fermee: false,
+ region: { code: "11", nom: "Île-de-France" },
+ academie: {
+ code: "01",
+ nom: "Paris",
+ },
+ donnee_source: {
+ code_certification: "12345678",
+ type: "self",
+ },
+ },
+ ],
+ pagination: {
+ nombre_de_page: 1,
+ page: 1,
+ items_par_page: 10,
+ total: 1,
+ },
+ });
+ });
+
it("Vérifie qu'on peut limiter le nombre de résultats", async () => {
const { httpClient } = await startServer();
await insertFormationsStats();
@@ -166,6 +275,24 @@ describe("formationsRoutes", () => {
assert.strictEqual(response.data.pagination.total, 1);
});
+ it("Vérifie qu'on peut obtenir les stats de formations pour un millesime unique", async () => {
+ const { httpClient } = await startServer();
+ await insertFormationsStats({ millesime: "2018_2019" });
+ await insertFormationsStats({ millesime: "2019" });
+ await insertFormationsStats({ millesime: "2020_2021" });
+
+ const response = await httpClient.get(`/api/inserjeunes/formations?millesimes=2019`, {
+ headers: {
+ ...getAuthHeaders(),
+ },
+ });
+
+ assert.strictEqual(response.status, 200);
+ assert.strictEqual(response.data.formations[0].millesime, "2018_2019");
+ assert.strictEqual(response.data.formations[1].millesime, "2019");
+ assert.strictEqual(response.data.pagination.total, 2);
+ });
+
it("Vérifie qu'on peut obtenir les stats de formations pour une région", async () => {
const { httpClient } = await startServer();
await insertFormationsStats({ region: { code: "76", nom: "Occitanie" } });
@@ -652,6 +779,86 @@ describe("formationsRoutes", () => {
});
});
+ it("Vérifie que l'on retourne en priorité un millésime unique si disponible", async () => {
+ const { httpClient } = await startServer();
+ await insertFormationsStats({
+ uai: "0751234J",
+ code_certification: "12345678",
+ millesime: "2018_2019",
+ });
+ await insertFormationsStats({
+ uai: "0751234J",
+ filiere: "apprentissage",
+ code_certification: "12345678",
+ code_formation_diplome: "12345678",
+ millesime: "2019",
+ nb_annee_term: 100,
+ nb_poursuite_etudes: 1,
+ nb_en_emploi_24_mois: 2,
+ nb_en_emploi_18_mois: 3,
+ nb_en_emploi_12_mois: 4,
+ nb_en_emploi_6_mois: 5,
+ nb_sortant: 6,
+ taux_rupture_contrats: 7,
+ taux_en_formation: 8,
+ taux_en_emploi_24_mois: 9,
+ taux_en_emploi_18_mois: 10,
+ taux_en_emploi_12_mois: 11,
+ taux_en_emploi_6_mois: 12,
+ taux_autres_6_mois: 13,
+ taux_autres_12_mois: 14,
+ taux_autres_18_mois: 15,
+ taux_autres_24_mois: 16,
+ });
+
+ const response = await httpClient.get(`/api/inserjeunes/formations/0751234J-12345678`);
+
+ assert.strictEqual(response.status, 200);
+ assert.deepStrictEqual(response.data, {
+ uai: "0751234J",
+ libelle_etablissement: "Lycée",
+ code_certification: "12345678",
+ code_certification_type: "cfd",
+ code_formation_diplome: "12345678",
+ libelle: "LIBELLE",
+ millesime: "2019",
+ filiere: "apprentissage",
+ diplome: { code: "4", libelle: "BAC" },
+ nb_annee_term: 100,
+ nb_poursuite_etudes: 1,
+ nb_en_emploi_24_mois: 2,
+ nb_en_emploi_18_mois: 3,
+ nb_en_emploi_12_mois: 4,
+ nb_en_emploi_6_mois: 5,
+ nb_sortant: 6,
+ taux_rupture_contrats: 7,
+ taux_en_formation: 8,
+ taux_en_emploi_24_mois: 9,
+ taux_en_emploi_18_mois: 10,
+ taux_en_emploi_12_mois: 11,
+ taux_en_emploi_6_mois: 12,
+ taux_autres_6_mois: 13,
+ taux_autres_12_mois: 14,
+ taux_autres_18_mois: 15,
+ taux_autres_24_mois: 16,
+ formation_fermee: false,
+ region: { code: "11", nom: "Île-de-France" },
+ academie: {
+ code: "01",
+ nom: "Paris",
+ },
+ donnee_source: {
+ code_certification: "12345678",
+ type: "self",
+ },
+ _meta: {
+ titre: "Certification 12345678, établissement 0751234J",
+ details:
+ "Données InserJeunes pour la certification 12345678 (BAC filière apprentissage) dispensée par l'établissement 0751234J, pour le millésime 2019",
+ },
+ });
+ });
+
it("Vérifie qu'on peut obtenir une formation avec le format XXX:XXX", async () => {
const { httpClient } = await startServer();
await insertFormationsStats({
@@ -766,7 +973,7 @@ describe("formationsRoutes", () => {
error: "Not Found",
message: "Pas de données pour le millésime",
data: {
- millesime: "2018_2019",
+ millesime: "2019",
millesimesDisponible: ["2017_2018"],
},
statusCode: 404,
@@ -792,6 +999,127 @@ describe("formationsRoutes", () => {
assert.deepStrictEqual(response.data.millesime, "2017_2018");
});
+ it("Vérifie qu'on peut obtenir une formation et un millesime unique", async () => {
+ const { httpClient } = await startServer();
+ await insertFormationsStats({
+ uai: "0751234J",
+ code_certification: "12345678",
+ millesime: "2018",
+ });
+ await insertFormationsStats({
+ uai: "0751234J",
+ code_certification: "12345678",
+ millesime: "2019",
+ });
+
+ const response = await httpClient.get(`/api/inserjeunes/formations/0751234J-12345678?millesime=2019`);
+
+ assert.strictEqual(response.status, 200);
+ assert.deepStrictEqual(response.data.millesime, "2019");
+ });
+
+ it("Vérifie qu'on obtient en priorité un millésime unique en demandant un millésime unique si les données sont disponibles également en aggrégés", async () => {
+ const { httpClient } = await startServer();
+ await insertFormationsStats({
+ uai: "0751234J",
+ code_certification: "12345678",
+ millesime: "2018_2019",
+ });
+ await insertFormationsStats({
+ uai: "0751234J",
+ code_certification: "12345678",
+ millesime: "2019",
+ });
+
+ const response = await httpClient.get(`/api/inserjeunes/formations/0751234J-12345678?millesime=2019`);
+
+ assert.strictEqual(response.status, 200);
+ assert.deepStrictEqual(response.data.millesime, "2019");
+ });
+
+ it("Vérifie qu'on peut obtenir une formation avec un millésime aggregé en demandant un millesime unique", async () => {
+ const { httpClient } = await startServer();
+ await insertFormationsStats({
+ uai: "0751234J",
+ code_certification: "12345678",
+ millesime: "2018",
+ });
+ await insertFormationsStats({
+ uai: "0751234J",
+ code_certification: "12345678",
+ millesime: "2018_2019",
+ });
+
+ const response = await httpClient.get(`/api/inserjeunes/formations/0751234J-12345678?millesime=2019`);
+
+ assert.strictEqual(response.status, 200);
+ assert.deepStrictEqual(response.data.millesime, "2018_2019");
+ });
+
+ it("Vérifie qu'on peut obtenir une formation avec un millésime unique en demandant un millesime aggregé", async () => {
+ const { httpClient } = await startServer();
+ await insertFormationsStats({
+ uai: "0751234J",
+ code_certification: "12345678",
+ millesime: "2019",
+ });
+
+ const response = await httpClient.get(`/api/inserjeunes/formations/0751234J-12345678?millesime=2018_2019`);
+
+ assert.strictEqual(response.status, 200);
+ assert.deepStrictEqual(response.data.millesime, "2019");
+ });
+
+ it("Ne retourne pas de stats en demandant un millésime unique si il ne correspond pas à la dernière année d'un millésime aggregé", async () => {
+ const { httpClient } = await startServer();
+ await insertCFD({ code_certification: "12345678" });
+ await insertAcceEtablissement({ numero_uai: "0751234J" });
+
+ await insertFormationsStats({
+ uai: "0751234J",
+ code_certification: "12345678",
+ millesime: "2019_2020",
+ });
+
+ const response = await httpClient.get(`/api/inserjeunes/formations/0751234J-12345678?millesime=2019`);
+
+ assert.strictEqual(response.status, 404);
+ assert.deepStrictEqual(response.data, {
+ error: "Not Found",
+ message: "Pas de données pour le millésime",
+ data: {
+ millesime: "2019",
+ millesimesDisponible: ["2019_2020"],
+ },
+ statusCode: 404,
+ });
+ });
+
+ it("Ne retourne pas de stats en demandant un millésime agregé si sa dernière année ne correspond pas à un millésime unique", async () => {
+ const { httpClient } = await startServer();
+ await insertCFD({ code_certification: "12345678" });
+ await insertAcceEtablissement({ numero_uai: "0751234J" });
+
+ await insertFormationsStats({
+ uai: "0751234J",
+ code_certification: "12345678",
+ millesime: "2019",
+ });
+
+ const response = await httpClient.get(`/api/inserjeunes/formations/0751234J-12345678?millesime=2019_2020`);
+
+ assert.strictEqual(response.status, 404);
+ assert.deepStrictEqual(response.data, {
+ error: "Not Found",
+ message: "Pas de données pour le millésime",
+ data: {
+ millesime: "2019_2020",
+ millesimesDisponible: ["2019"],
+ },
+ statusCode: 404,
+ });
+ });
+
it("Vérifie qu'on retourne une 404 si la formation est inconnue", async () => {
const { httpClient } = await startServer();
@@ -807,7 +1135,7 @@ describe("formationsRoutes", () => {
});
describe("Widget", () => {
- function createDefaultStats() {
+ function createDefaultStats(data = {}) {
return insertFormationsStats({
uai: "0751234J",
code_certification: "10221058",
@@ -815,6 +1143,7 @@ describe("formationsRoutes", () => {
taux_en_formation: 25,
taux_autres_6_mois: 12,
nb_annee_term: 20,
+ ...data,
});
}
@@ -837,6 +1166,22 @@ describe("formationsRoutes", () => {
expect(svgFixture).not.differentFrom(response.data, { relaxedSpace: true });
});
+ it("Vérifie qu'on peut obtenir une image SVG pour un millésime unique", async () => {
+ const { httpClient } = await startServer();
+ await createDefaultStats({ millesime: "2019" });
+
+ const response = await httpClient.get("/api/inserjeunes/formations/0751234J-10221058.svg?theme=" + theme);
+
+ assert.strictEqual(response.status, 200);
+ assert.ok(response.headers["content-type"].includes("image/svg+xml"));
+
+ const svgFixture = await fs.promises.readFile(
+ `tests/fixtures/widgets/${theme}/formations/0751234J-10221058_2019.svg`,
+ "utf8"
+ );
+ expect(svgFixture).not.differentFrom(response.data, { relaxedSpace: true });
+ });
+
it("Vérifie qu'on peut obtenir une image SVG horizontale", async () => {
const { httpClient } = await startServer();
await createDefaultStats();
@@ -1136,6 +1481,34 @@ describe("formationsRoutes", () => {
expect(autresBlock).to.contain.text("20%");
});
+ it("Vérifie qu'on obtient un widget pour un millésime unique", async () => {
+ const { httpClient } = await startServer();
+ await createDefaultStats({ millesime: "2019" });
+ const response = await httpClient.get("/api/inserjeunes/formations/0751234J-10221058/widget/test");
+ assert.strictEqual(response.status, 200);
+
+ const dom = new JSDOM(response.data);
+
+ const subTitle = dom.window.document.querySelector(".container .subTitle");
+ expect(subTitle).to.contain.text("Lycée professionnel");
+
+ const emploiBlock = dom.window.document.querySelector(".block-emploi");
+ expect(emploiBlock).to.contain.text("TRAVAILLENT");
+ expect(emploiBlock).to.contain.text("50%");
+
+ const formationBlock = dom.window.document.querySelector(".block-formation");
+ expect(formationBlock).to.contain.text("ÉTUDIENT");
+ expect(formationBlock).to.contain.text("30%");
+
+ const autresBlock = dom.window.document.querySelector(".block-autres");
+ expect(autresBlock).to.contain.text("AUTRES PARCOURS");
+ expect(autresBlock).to.contain.text("20%");
+
+ const footer = dom.window.document.querySelector(".card-footer");
+ expect(footer).to.contain.text("promotions 2019");
+ expect(footer).to.not.contain.text("promotions 2018 et 2019");
+ });
+
it("Vérifie qu'on obtient un widget pour un code au format XXX:XXX", async () => {
const { httpClient } = await startServer();
await createDefaultStats();
diff --git a/server/tests/jobs/stats/importFormationsSupStats-test.js b/server/tests/jobs/stats/importFormationsSupStats-test.js
index 37f16aff..221a9e13 100644
--- a/server/tests/jobs/stats/importFormationsSupStats-test.js
+++ b/server/tests/jobs/stats/importFormationsSupStats-test.js
@@ -104,6 +104,156 @@ describe("importFormationsSupStats", () => {
assert.deepStrictEqual(stats, { created: 1, failed: 0, updated: 0 });
});
+ it("Vérifie qu'on peut importer les stats d'une formation (superieur) pour des millésimes simples et aggrégé", async () => {
+ const formations = await Fixtures.FormationsInserSupMillesimesMixtes();
+ await stubApi("0062205P", "2020_2021", formations);
+
+ await insertBCNSise({
+ diplome_sise: "2500200",
+ });
+
+ await insertAcceEtablissement({
+ numero_uai: "0062205P",
+ });
+
+ const stats = await importFormationsSupStats({
+ millesimes: ["2020_2021"],
+ });
+
+ const found = await formationsStats().findOne({ millesime: "2020_2021" }, { projection: { _id: 0 } });
+ assert.deepStrictEqual(found, {
+ uai: "0062205P",
+ code_certification: "2500200",
+ code_certification_type: "sise",
+ libelle: "METIERS DE L'ENSEIGNEMENT",
+ libelle_etablissement: "Université Côte d'Azur",
+ millesime: "2020_2021",
+ filiere: "superieur",
+ date_fermeture: new Date("2023-01-01T00:00:00.000Z"),
+ nb_annee_term: 30,
+ nb_diplome: 30,
+ nb_en_emploi_12_mois: 14,
+ nb_en_emploi_18_mois: 13,
+ nb_en_emploi_6_mois: 11,
+ nb_poursuite_etudes: 8,
+ nb_sortant: 22,
+ taux_autres_12_mois: 26,
+ taux_autres_18_mois: 30,
+ taux_autres_6_mois: 36,
+ taux_en_emploi_12_mois: 47,
+ taux_en_emploi_18_mois: 43,
+ taux_en_emploi_6_mois: 37,
+ taux_en_formation: 27,
+ diplome: {
+ code: "6",
+ libelle: "MAST ENS",
+ },
+ region: {
+ code: "93",
+ nom: "Provence-Alpes-Côte d'Azur",
+ },
+ academie: { code: "23", nom: "Nice" },
+ donnee_source: {
+ code_certification: "2500200",
+ type: "self",
+ },
+ _meta: {
+ insersup: {
+ discipline: "STAPS",
+ domaine_disciplinaire: "Sciences, technologies, santé",
+ etablissement_actuel_libelle: "Université Côte d'Azur",
+ etablissement_libelle: "Université Côte d'Azur",
+ secteur_disciplinaire: "STAPS",
+ type_diplome: "Master LMD",
+ },
+ created_on: new Date("2023-01-01T00:00:00.000Z"),
+ updated_on: new Date("2023-01-01T00:00:00.000Z"),
+ date_import: new Date("2023-01-01T00:00:00.000Z"),
+ },
+ });
+
+ const found2 = await formationsStats().findOne({ millesime: "2020" }, { projection: { _id: 0 } });
+ assert.deepStrictEqual(found2, {
+ uai: "0062205P",
+ code_certification: "2500200",
+ code_certification_type: "sise",
+ libelle: "METIERS DE L'ENSEIGNEMENT",
+ libelle_etablissement: "Université Côte d'Azur",
+ millesime: "2020",
+ filiere: "superieur",
+ date_fermeture: new Date("2023-01-01T00:00:00.000Z"),
+ nb_annee_term: 141,
+ nb_diplome: 141,
+ nb_en_emploi_12_mois: 84,
+ nb_en_emploi_18_mois: 100,
+ nb_en_emploi_24_mois: 103,
+ nb_en_emploi_6_mois: 81,
+ nb_poursuite_etudes: 24,
+ nb_sortant: 117,
+ taux_autres_12_mois: 23,
+ taux_autres_18_mois: 12,
+ taux_autres_24_mois: 10,
+ taux_autres_6_mois: 26,
+ taux_en_emploi_12_mois: 60,
+ taux_en_emploi_18_mois: 71,
+ taux_en_emploi_24_mois: 73,
+ taux_en_emploi_6_mois: 57,
+ taux_en_formation: 17,
+ diplome: {
+ code: "6",
+ libelle: "MAST ENS",
+ },
+ region: {
+ code: "93",
+ nom: "Provence-Alpes-Côte d'Azur",
+ },
+ academie: { code: "23", nom: "Nice" },
+ donnee_source: {
+ code_certification: "2500200",
+ type: "self",
+ },
+ _meta: {
+ insersup: {
+ discipline: "Sciences humaines et sociales",
+ domaine_disciplinaire: "Sciences humaines et sociales",
+ etablissement_actuel_libelle: "Université Côte d'Azur",
+ etablissement_libelle: "Université Côte d'Azur",
+ secteur_disciplinaire: "Sciences de l'éducation",
+ type_diplome: "Master MEEF",
+ },
+ created_on: new Date("2023-01-01T00:00:00.000Z"),
+ updated_on: new Date("2023-01-01T00:00:00.000Z"),
+ date_import: new Date("2023-01-01T00:00:00.000Z"),
+ },
+ });
+ assert.deepStrictEqual(stats, { created: 2, failed: 0, updated: 0 });
+ });
+
+ it("Vérifie que l'on a une erreur si un millésime simple et également la dernière année d'un millésime aggrégé", async () => {
+ const formations = await Fixtures.FormationsInserSupMillesimesMixtes(true);
+ await stubApi("0062205P", "2020_2021", formations);
+
+ await insertBCNSise({
+ diplome_sise: "2500200",
+ });
+
+ await insertAcceEtablissement({
+ numero_uai: "0062205P",
+ });
+
+ const stats = await importFormationsSupStats({
+ millesimes: ["2020_2021"],
+ });
+
+ assert.deepStrictEqual(stats, {
+ created: 3,
+ failed: 1,
+ updated: 0,
+ error:
+ 'Millésime en double pour : {"uai":"0062205P","code_certification":"2500200","filiere":"superieur","millesimes":["2021","2020_2021"]}',
+ });
+ });
+
it("Vérifie que l'on agrège pas les stats si elles sont disponibles par millesime", async () => {
const formations = await Fixtures.FormationsInserSup(false);
await stubApi("0062205P", "2020_2021", formations);
@@ -126,76 +276,6 @@ describe("importFormationsSupStats", () => {
assert.deepStrictEqual(stats, { created: 0, failed: 0, updated: 0 });
});
- // it("Vérifie qu'on agrège les stats si elles sont disponible par millesime", async () => {
- // const formations = await Fixtures.FormationsInserSup(false);
- // await stubApi("0062205P", "2020_2021", formations);
-
- // await insertBCNSise({
- // diplome_sise: "2500249",
- // });
-
- // await insertAcceEtablissement({
- // numero_uai: "0062205P",
- // });
-
- // const stats = await importFormationsSupStats({
- // parameters: [{ millesime: "2020_2021" }],
- // });
-
- // const found = await formationsStats().findOne({}, { projection: { _id: 0 } });
- // assert.deepStrictEqual(found, {
- // uai: "0062205P",
- // code_certification: "2500249",
- // code_certification_type: "sise",
- // libelle: "METIERS DE L'ENSEIGNEMENT",
- // millesime: "2020_2021",
- // filiere: "superieur",
- // date_fermeture: new Date("2023-01-01T00:00:00.000Z"),
- // nb_annee_term: 272,
- // nb_diplome: 272,
- // nb_en_emploi_12_mois: 195,
- // nb_en_emploi_18_mois: 211,
- // nb_en_emploi_6_mois: 188,
- // nb_poursuite_etudes: 35,
- // nb_sortant: 237,
- // taux_autres_12_mois: 15,
- // taux_autres_18_mois: 9,
- // taux_autres_6_mois: 18,
- // taux_en_emploi_12_mois: 72,
- // taux_en_emploi_18_mois: 78,
- // taux_en_emploi_6_mois: 69,
- // taux_en_formation: 13,
- // diplome: {
- // code: "6",
- // libelle: "MAST ENS",
- // },
- // region: {
- // code: "93",
- // nom: "Provence-Alpes-Côte d'Azur",
- // },
- // academie: { code: "23", nom: "Nice" },
- // donnee_source: {
- // code_certification: "2500249",
- // type: "self",
- // },
- // _meta: {
- // insersup: {
- // discipline: "Sciences humaines et sociales",
- // domaine_disciplinaire: "Sciences humaines et sociales",
- // etablissement_actuel_libelle: "Université Côte d'Azur",
- // etablissement_libelle: "Université Côte d'Azur",
- // secteur_disciplinaire: "Sciences de l'éducation",
- // type_diplome: "Master MEEF",
- // },
- // created_on: new Date("2023-01-01T00:00:00.000Z"),
- // updated_on: new Date("2023-01-01T00:00:00.000Z"),
- // date_import: new Date("2023-01-01T00:00:00.000Z"),
- // },
- // });
-
- // assert.deepStrictEqual(stats, { created: 1, failed: 0, updated: 0 });
- // });
-
it("Vérifie qu'on n'agrège pas les stats si elles ne sont disponible que pour un millesime", async () => {
const formations = await Fixtures.FormationsInserSupInvalid();
await stubApi("0062205P", "2020_2021", formations);
diff --git a/server/tests/utils/fixtures.js b/server/tests/utils/fixtures.js
index 77477ca6..30968d3b 100644
--- a/server/tests/utils/fixtures.js
+++ b/server/tests/utils/fixtures.js
@@ -30,6 +30,14 @@ export async function FormationsInserSup(twoMillesimes = false) {
return readJson("../fixtures/files/inserSup/formations.json");
}
+export async function FormationsInserSupMillesimesMixtes(withDouble = false) {
+ if (withDouble) {
+ return readJson("../fixtures/files/inserSup/formationsMillesimesMixtesWithDouble.json");
+ }
+
+ return readJson("../fixtures/files/inserSup/formationsMillesimesMixtes.json");
+}
+
export async function FormationsInserSupInvalid() {
return readJson("../fixtures/files/inserSup/formationsInvalid.json");
}