From 94f9dbb7e82c162a55602bb9204bf2723ba9251d Mon Sep 17 00:00:00 2001 From: Ananda Date: Wed, 20 Nov 2024 16:47:45 +0100 Subject: [PATCH] feat: millesime unique pour insersup --- .../src/common/repositories/formationStats.js | 39 ++ server/src/common/stats.js | 21 + server/src/http/routes/formationsRoutes.js | 59 ++- server/src/http/utils/widgetUtils.js | 4 +- server/src/jobs/stats/computeUAI.js | 12 + .../jobs/stats/importFormationsSupStats.js | 21 + .../services/dataEnseignementSup/InserSup.js | 52 +-- .../inserSup/formationsMillesimesMixtes.json | 250 ++++++++++++ .../formationsMillesimesMixtesWithDouble.json | 355 +++++++++++++++++ .../formations/0751234J-10221058_2019.svg | 208 ++++++++++ .../lba/formations/0751234J-10221058_2019.svg | 153 +++++++ server/tests/http/formationsRoutes-test.js | 377 +++++++++++++++++- .../stats/importFormationsSupStats-test.js | 220 ++++++---- server/tests/utils/fixtures.js | 8 + 14 files changed, 1648 insertions(+), 131 deletions(-) create mode 100644 server/tests/fixtures/files/inserSup/formationsMillesimesMixtes.json create mode 100644 server/tests/fixtures/files/inserSup/formationsMillesimesMixtesWithDouble.json create mode 100644 server/tests/fixtures/widgets/dsfr/formations/0751234J-10221058_2019.svg create mode 100644 server/tests/fixtures/widgets/lba/formations/0751234J-10221058_2019.svg 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 @@ + + + + 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 + + + + + + + + + + + + + + + + + + + + 50% + + + sont en emploi 6 mois après la + fin de la formation + + + (tout type d'emploi salarié du privé) + + + + + + + + + + + 25% + + + sont inscrits en formation + + + (Formation supérieure,redoublants, + changement de filière) + + + + + + + + + + 12% + + + sont dans d’autres situations + + + (recherche d’emploi, service civique, + à l’étranger, indépendant, etc.) + + + + + + + + + *Données issues du dispositif InserJeunes + + + promotion 2019 + + + + + + + + + + + + Que deviennent les + apprenants après cette + formation ? + Les chiffres pour cet établissement + + + + + \ 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"); }