Files

154 lines
6.1 KiB
JavaScript
Raw Permalink Normal View History

2026-04-02 14:15:26 +02:00
// =============================================================================
// api.js — Gestion des requêtes vers l'API publique Parcoursup (data.gouv.fr)
//
// Objectif pour la soutenance : Montrer qu'on sait forger dynamiquement une URL
// avec des filtres complexes et gérer des requêtes asynchrones (async/await)
// pour récupérer l'historique d'une formation sur plusieurs années.
// =============================================================================
// Échappe les apostrophes pour éviter de casser la syntaxe de la requête de type SQL
// Ex: "Licence d'histoire" -> "Licence d\'histoire"
2026-03-30 15:05:11 +02:00
function echapperValeur(valeur) {
2026-03-30 16:33:11 +02:00
return String(valeur).replace(/'/g, "\\'");
2026-03-30 15:05:11 +02:00
}
2026-04-02 14:15:26 +02:00
// Construit l'URL avec les paramètres de pagination et la clause "where" contenant les filtres
2026-03-21 13:47:09 +01:00
export function construireURL(requete, limite = 20, decalage = 0, filtres = {}) {
2026-03-30 16:33:11 +02:00
var url = "https://data.enseignementsup-recherche.gouv.fr/api/explore/v2.1/catalog/datasets/fr-esr-parcoursup/records?";
2026-03-21 13:47:09 +01:00
2026-04-02 14:15:26 +02:00
// Paramètres de base pour la pagination (limite de résultats et offset)
url += "limit=" + limite;
2026-03-30 16:33:11 +02:00
url += "&offset=" + decalage;
2026-03-30 16:33:11 +02:00
var conditions = [];
2026-03-20 01:51:08 +01:00
2026-04-02 14:15:26 +02:00
// Recherche plein texte : utilise la commande "search" spécifique de l'API ODS (OpenDataSoft)
2026-03-21 13:47:09 +01:00
if (requete && requete.trim() !== "") {
2026-03-30 16:33:11 +02:00
conditions.push("search(lib_for_voe_ins, '" + echapperValeur(requete.trim()) + "')");
2026-03-20 01:51:08 +01:00
}
2026-04-02 14:15:26 +02:00
// Filtrage par filière (BTS, BUT, Licence...)
2026-03-21 13:47:09 +01:00
if (filtres.filiere && filtres.filiere !== "") {
2026-03-30 16:33:11 +02:00
conditions.push("fili='" + echapperValeur(filtres.filiere) + "'");
2026-03-20 01:51:08 +01:00
}
2026-04-02 14:15:26 +02:00
// Filtrage par sélectivité (sélective / non sélective)
2026-03-21 13:47:09 +01:00
if (filtres.selectivite && filtres.selectivite !== "") {
2026-03-30 16:33:11 +02:00
conditions.push("select_form='" + echapperValeur(filtres.selectivite) + "'");
2026-03-20 01:51:08 +01:00
}
2026-04-02 14:15:26 +02:00
// Filtrage par région géographique de l'établissement
2026-03-21 13:47:09 +01:00
if (filtres.region && filtres.region !== "") {
2026-03-30 16:33:11 +02:00
conditions.push("region_etab_aff='" + echapperValeur(filtres.region) + "'");
2026-03-20 01:51:08 +01:00
}
2026-04-02 14:15:26 +02:00
// Application de filtres numériques (taux d'accès minimum et maximum)
2026-03-21 13:47:09 +01:00
if (filtres.tauxMin && filtres.tauxMin > 0) {
2026-03-30 16:33:11 +02:00
conditions.push("taux_acces_ens>=" + filtres.tauxMin);
2026-03-20 01:51:08 +01:00
}
2026-03-21 13:47:09 +01:00
if (filtres.tauxMax && filtres.tauxMax < 100) {
2026-03-30 16:33:11 +02:00
conditions.push("taux_acces_ens<=" + filtres.tauxMax);
2026-03-20 01:51:08 +01:00
}
2026-04-02 14:15:26 +02:00
// Assemblage de toutes les conditions séparées par un opérateur ET logique
// On utilise encodeURIComponent pour assurer la validité de l'URL finale
2026-03-20 01:51:08 +01:00
if (conditions.length > 0) {
2026-03-30 16:33:11 +02:00
url += "&where=" + encodeURIComponent(conditions.join(" AND "));
2026-03-17 17:34:34 +01:00
}
2026-03-30 16:33:11 +02:00
return url;
}
2026-04-02 14:15:26 +02:00
// Effectue la requête HTTP vers l'URL construite
// Fonction asynchrone qui retourne une promesse résolue avec les résultats JSON
2026-03-21 13:47:09 +01:00
export async function chargerFormations(requete, limite = 20, decalage = 0, filtres = {}) {
2026-03-30 16:33:11 +02:00
var url = construireURL(requete, limite, decalage, filtres);
var reponse = await fetch(url);
2026-03-21 13:47:09 +01:00
2026-04-02 14:15:26 +02:00
// Gestion des erreurs HTTP (ex: 400 Bad Request, 500 Internal Server Error)
2026-03-21 13:47:09 +01:00
if (!reponse.ok) {
2026-03-30 16:33:11 +02:00
throw new Error("Erreur HTTP " + reponse.status);
}
2026-04-02 14:15:26 +02:00
// Extraction et retour du corps de la réponse au format JSON
2026-03-30 16:33:11 +02:00
return await reponse.json();
2026-03-20 01:51:08 +01:00
}
2026-04-02 14:15:26 +02:00
// Fonction métier complexe : reconstitue l'historique d'une formation sur plusieurs années
// Démontre l'orchestration de multiples appels API asynchrones
2026-03-21 13:47:09 +01:00
export async function chargerHistoriqueFormation(codUai, nomFormation) {
2026-04-02 14:15:26 +02:00
// Table de correspondance entre l'année et le nom du dataset sur open data
2026-03-21 13:47:09 +01:00
var jeuDeDonnees = {
2026-03-20 01:51:08 +01:00
2020: "fr-esr-parcoursup_2020",
2021: "fr-esr-parcoursup_2021",
2022: "fr-esr-parcoursup_2022",
2023: "fr-esr-parcoursup_2023",
2024: "fr-esr-parcoursup_2024",
2026-04-02 14:15:26 +02:00
2025: "fr-esr-parcoursup" // L'année en cours est sur le dataset par défaut
2026-03-30 16:33:11 +02:00
};
2026-03-20 01:51:08 +01:00
2026-03-30 16:33:11 +02:00
var historique = [];
var nomCourt = echapperValeur((nomFormation || "").substring(0, 40));
var codeUai = echapperValeur(codUai);
var annees = [2020, 2021, 2022, 2023, 2024, 2025];
2026-03-20 01:51:08 +01:00
2026-04-02 14:15:26 +02:00
// Requête itérative pour consolider les données par année
2026-03-21 13:47:09 +01:00
for (var i = 0; i < annees.length; i++) {
2026-03-30 16:33:11 +02:00
var annee = annees[i];
var dataset = jeuDeDonnees[annee];
2026-03-20 01:51:08 +01:00
try {
2026-04-02 14:15:26 +02:00
// On croise le code établissement (cod_uai) avec le nom de formation pour être précis
var where = "cod_uai='" + codeUai + "' AND search(lib_for_voe_ins, '" + nomCourt + "')";
2026-03-21 13:47:09 +01:00
2026-04-02 14:15:26 +02:00
// On limite à 5 résultats max et on sélectionne uniquement les colonnes nécessaires (optimisation)
2026-03-20 01:51:08 +01:00
var url = "https://data.enseignementsup-recherche.gouv.fr/api/explore/v2.1/catalog/datasets/"
+ dataset + "/records?"
+ "limit=5"
2026-03-30 15:05:11 +02:00
+ "&where=" + encodeURIComponent(where)
2026-03-30 16:33:11 +02:00
+ "&select=" + encodeURIComponent("cod_uai,lib_for_voe_ins,voe_tot,acc_tot,pct_sansmention,pct_ab,pct_b,pct_tb,pct_tbf,pct_bg,pct_bt,pct_bp");
2026-03-20 01:51:08 +01:00
2026-03-30 16:33:11 +02:00
var reponse = await fetch(url);
2026-03-20 01:51:08 +01:00
2026-03-21 13:47:09 +01:00
if (reponse.ok) {
2026-03-30 16:33:11 +02:00
var donnees = await reponse.json();
2026-03-20 01:51:08 +01:00
2026-03-21 13:47:09 +01:00
if (donnees.results && donnees.results.length > 0) {
2026-04-02 14:15:26 +02:00
var ligne = donnees.results[0]; // On prend le premier résultat trouvé
2026-03-30 16:33:11 +02:00
var taux = 0;
2026-03-21 13:47:09 +01:00
2026-04-02 14:15:26 +02:00
// Calcul mathématique propre du taux d'accès pour éviter une division par zéro
2026-03-21 13:47:09 +01:00
if (ligne.voe_tot && ligne.voe_tot > 0) {
2026-03-30 16:33:11 +02:00
taux = Math.round((ligne.acc_tot / ligne.voe_tot) * 100);
2026-03-20 01:51:08 +01:00
}
2026-04-02 14:15:26 +02:00
// Formatage d'un objet normalisé pour le graphique Riot
2026-03-21 13:47:09 +01:00
historique.push({
annee: annee,
tauxAcces: taux,
candidats: ligne.voe_tot || 0,
admis: ligne.acc_tot || 0,
pctSansMention: ligne.pct_sansmention || 0,
2026-04-02 14:15:26 +02:00
pctAB: ligne.pct_ab || 0,
pctB: ligne.pct_b || 0,
pctTB: ligne.pct_tb || 0,
2026-03-21 13:47:09 +01:00
pctTBF: ligne.pct_tbf || 0,
2026-04-02 14:15:26 +02:00
pctGeneral: ligne.pct_bg || 0,
pctTechno: ligne.pct_bt || 0,
pctPro: ligne.pct_bp || 0
2026-03-30 16:33:11 +02:00
});
2026-03-20 01:51:08 +01:00
}
}
2026-03-21 13:47:09 +01:00
2026-03-20 01:51:08 +01:00
} catch (e) {
2026-04-02 14:15:26 +02:00
// Le bloc try/catch empêche l'échec de la boucle entière si une seule année est en erreur (ex: dataset absent)
console.warn("L'année " + annee + " n'a pas pu être récupérée :", e);
2026-03-20 01:51:08 +01:00
}
}
2026-03-30 16:33:11 +02:00
return historique;
2026-03-30 15:05:11 +02:00
}