mAj
This commit is contained in:
+43
-67
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user