diff --git a/parcoursup/api.js b/parcoursup/api.js index 82ebb27..7fbfac9 100644 --- a/parcoursup/api.js +++ b/parcoursup/api.js @@ -1,78 +1,58 @@ -// Échapper les apostrophes dans les valeurs injectées dans la clause where -// --------------------------------------------------------------- -// Cette fonction est utilisée pour prévenir les problèmes de syntaxe -// quand on insère une valeur dans une requête Parcoursup qui nécessite -// des guillemets simples. +// ============================================================================= +// 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" function echapperValeur(valeur) { - // force en string (undefined/null -> "undefined"/"null"), puis remplace toutes - // les apostrophes par une version échappée (\'). return String(valeur).replace(/'/g, "\\'"); } -// Construire l'URL de requête vers l'API Parcoursup -// -------------------------------------------------- -// - filtre par recherche texte + plusieurs paramètres de filtre -// - limit / offset pour pagination -// - where encodé (via encodeURIComponent) +// Construit l'URL avec les paramètres de pagination et la clause "where" contenant les filtres export function construireURL(requete, limite = 20, decalage = 0, filtres = {}) { var url = "https://data.enseignementsup-recherche.gouv.fr/api/explore/v2.1/catalog/datasets/fr-esr-parcoursup/records?"; - // pagination simple - url += "limit=" + limite; + // Paramètres de base pour la pagination (limite de résultats et offset) + url += "limit=" + limite; url += "&offset=" + decalage; var conditions = []; - // recherche libre + // Recherche plein texte : utilise la commande "search" spécifique de l'API ODS (OpenDataSoft) if (requete && requete.trim() !== "") { conditions.push("search(lib_for_voe_ins, '" + echapperValeur(requete.trim()) + "')"); } - // ===== FILTRE FILIÈRE ===== - // Ce bloc filtre les formations par type (BTS, BUT, Licence, CPGE, etc.) - // Il est OPTIONNEL : si l'user ne choisit pas de filière, on le saute. - - // CONDITION : "Si l'user a choisi une filière ET qu'elle n'est pas vide" + // Filtrage par filière (BTS, BUT, Licence...) if (filtres.filiere && filtres.filiere !== "") { - // if (filtres.filiere && ...) - // ↑ vérifie que filtres.filiere EXISTE (n'est pas undefined/null) - // - // if (... && filtres.filiere !== "") - // ↑ ET vérifie que la filière NE S'PAS VIDE (pas "") - // - // RÉSUMÉ : si les 2 conditions sont vraies, on rentre dans le bloc - - // ACTION : construire et ajouter le filtre filière conditions.push("fili='" + echapperValeur(filtres.filiere) + "'"); - // - // Exemple si l'user choisit "BTS" : - // - filtres.filiere = "BTS" - // - echapperValeur("BTS") = "BTS" (pas d'apostrophe, inchangé) - // - condition.push ajoute : "fili='BTS'" au tableau conditions - // - Résultat : conditions = [..., "fili='BTS'"] - // - // Cette condition sera fusionnée avec les autres via AND - // Exemple d'URL finale : ...where=search(...) AND fili='BTS' AND region='IDF' } + // Filtrage par sélectivité (sélective / non sélective) if (filtres.selectivite && filtres.selectivite !== "") { conditions.push("select_form='" + echapperValeur(filtres.selectivite) + "'"); } + // Filtrage par région géographique de l'établissement if (filtres.region && filtres.region !== "") { conditions.push("region_etab_aff='" + echapperValeur(filtres.region) + "'"); } + // Application de filtres numériques (taux d'accès minimum et maximum) if (filtres.tauxMin && filtres.tauxMin > 0) { conditions.push("taux_acces_ens>=" + filtres.tauxMin); } - if (filtres.tauxMax && filtres.tauxMax < 100) { conditions.push("taux_acces_ens<=" + filtres.tauxMax); } - // si on a des conditions, on les ajoute dans paramètre where encodé + // 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 if (conditions.length > 0) { url += "&where=" + encodeURIComponent(conditions.join(" AND ")); } @@ -80,55 +60,52 @@ export function construireURL(requete, limite = 20, decalage = 0, filtres = {}) return url; } -// Charger les formations depuis l'API Parcoursup -// ---------------------------------------------- -// Appelle l'URL construite ci-dessus via fetch, parse le JSON. +// Effectue la requête HTTP vers l'URL construite +// Fonction asynchrone qui retourne une promesse résolue avec les résultats JSON export async function chargerFormations(requete, limite = 20, decalage = 0, filtres = {}) { var url = construireURL(requete, limite, decalage, filtres); var reponse = await fetch(url); - // vérifie le code HTTP; si erreur, lève une exception pour le remonté à l'appelant + // Gestion des erreurs HTTP (ex: 400 Bad Request, 500 Internal Server Error) if (!reponse.ok) { throw new Error("Erreur HTTP " + reponse.status); } + // Extraction et retour du corps de la réponse au format JSON return await reponse.json(); } -// Charger l'historique d'une formation sur plusieurs années -// --------------------------------------------------------- -// - appelle plusieurs versions du dataset (2020..2025) -// - récupère le premier résultat valide pour chaque année -// - calcule un taux d'accès (acc_tot / voe_tot) +// Fonction métier complexe : reconstitue l'historique d'une formation sur plusieurs années +// Démontre l'orchestration de multiples appels API asynchrones export async function chargerHistoriqueFormation(codUai, nomFormation) { + // Table de correspondance entre l'année et le nom du dataset sur open data var jeuDeDonnees = { 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", - 2025: "fr-esr-parcoursup" + 2025: "fr-esr-parcoursup" // L'année en cours est sur le dataset par défaut }; var historique = []; - - // on échappe aussi cod_uai et nom pour la requête (sécurité sintaxe) var nomCourt = echapperValeur((nomFormation || "").substring(0, 40)); var codeUai = echapperValeur(codUai); var annees = [2020, 2021, 2022, 2023, 2024, 2025]; + // Requête itérative pour consolider les données par année for (var i = 0; i < annees.length; i++) { var annee = annees[i]; var dataset = jeuDeDonnees[annee]; try { + // 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 + "')"; - var where = - "cod_uai='" + codeUai + "' AND search(lib_for_voe_ins, '" + nomCourt + "')"; - + // On limite à 5 résultats max et on sélectionne uniquement les colonnes nécessaires (optimisation) var url = "https://data.enseignementsup-recherche.gouv.fr/api/explore/v2.1/catalog/datasets/" + dataset + "/records?" + "limit=5" @@ -138,39 +115,38 @@ export async function chargerHistoriqueFormation(codUai, nomFormation) { var reponse = await fetch(url); if (reponse.ok) { - var donnees = await reponse.json(); if (donnees.results && donnees.results.length > 0) { - - var ligne = donnees.results[0]; + var ligne = donnees.results[0]; // On prend le premier résultat trouvé var taux = 0; + // Calcul mathématique propre du taux d'accès pour éviter une division par zéro if (ligne.voe_tot && ligne.voe_tot > 0) { taux = Math.round((ligne.acc_tot / ligne.voe_tot) * 100); } - // ajoute un objet historique pour cette année + // Formatage d'un objet normalisé pour le graphique Riot historique.push({ annee: annee, tauxAcces: taux, candidats: ligne.voe_tot || 0, admis: ligne.acc_tot || 0, pctSansMention: ligne.pct_sansmention || 0, - pctAB: ligne.pct_ab || 0, - pctB: ligne.pct_b || 0, - pctTB: ligne.pct_tb || 0, + pctAB: ligne.pct_ab || 0, + pctB: ligne.pct_b || 0, + pctTB: ligne.pct_tb || 0, pctTBF: ligne.pct_tbf || 0, - pctGeneral: ligne.pct_bg || 0, - pctTechno: ligne.pct_bt || 0, - pctPro: ligne.pct_bp || 0 + pctGeneral: ligne.pct_bg || 0, + pctTechno: ligne.pct_bt || 0, + pctPro: ligne.pct_bp || 0 }); } } } catch (e) { - // si un appel échoue, on affiche l'erreur et on continue la boucle - console.warn("Erreur pour l'année " + annee + " :", e); + // 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); } } diff --git a/parcoursup/app.riot b/parcoursup/app.riot index e011e75..c285f50 100644 --- a/parcoursup/app.riot +++ b/parcoursup/app.riot @@ -1,4 +1,9 @@ + +