Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import pandas as pd | |
| import matplotlib.pyplot as plt | |
| from pathlib import Path | |
| from datetime import date | |
| import numpy as np | |
| import tempfile | |
| # Fichier CSV stocké dans le dossier du Space (/app/grades.csv) | |
| DATA_PATH = Path("grades.csv") | |
| def download_plot(student): | |
| """Génère un fichier PNG du graphique et le retourne pour téléchargement.""" | |
| fig = plot_student(student) # on récupère la figure existante | |
| tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".png") | |
| fig.savefig(tmp.name, dpi=150, bbox_inches="tight") | |
| return tmp.name | |
| def load_data(): | |
| """Charge les données depuis grades.csv ou crée un DataFrame vide.""" | |
| if DATA_PATH.exists(): | |
| df = pd.read_csv(DATA_PATH) | |
| # Normalisation basique des noms de colonnes pour être robuste | |
| col_map = {} | |
| for c in df.columns: | |
| cl = c.lower() | |
| if cl in ("etudiant", "étudiant", "eleve", "élève"): | |
| col_map[c] = "eleve" | |
| elif cl in ("matiere", "matière", "devoir"): | |
| col_map[c] = "devoir" | |
| elif cl == "note": | |
| col_map[c] = "note" | |
| elif cl == "date": | |
| col_map[c] = "date" | |
| if col_map: | |
| df = df.rename(columns=col_map) | |
| # On garantit la présence des 4 colonnes | |
| for col in ["eleve", "date", "devoir", "note"]: | |
| if col not in df.columns: | |
| df[col] = pd.NA | |
| return df[["eleve", "date", "devoir", "note"]] | |
| else: | |
| return pd.DataFrame(columns=["eleve", "date", "devoir", "note"]) | |
| # DataFrame global en mémoire | |
| df = load_data() | |
| def get_eleve_list(df_local: pd.DataFrame): | |
| if df_local.empty: | |
| return [] | |
| return sorted(df_local["eleve"].dropna().unique().tolist()) | |
| def get_devoir_list(df_local: pd.DataFrame): | |
| if df_local.empty: | |
| return [] | |
| return sorted(df_local["devoir"].dropna().unique().tolist()) | |
| def add_grade(eleve, devoir, note, date_input): | |
| """Ajoute une note au DataFrame + sauvegarde CSV.""" | |
| global df | |
| eleve = (eleve or "").strip() | |
| devoir = (devoir or "").strip() | |
| if not eleve: | |
| return ( | |
| "Erreur : le nom de l'élève est obligatoire.", | |
| gr.update(), | |
| df, | |
| gr.update(), | |
| ) | |
| try: | |
| note_float = float(note) | |
| except Exception: | |
| return ( | |
| "Erreur : la note doit être un nombre.", | |
| gr.update(), | |
| df, | |
| gr.update(), | |
| ) | |
| try: | |
| if date_input: | |
| d = pd.to_datetime(date_input).date() | |
| else: | |
| d = date.today() | |
| except Exception: | |
| d = date.today() | |
| new_row = { | |
| "eleve": eleve, | |
| "date": d.isoformat(), | |
| "devoir": devoir if devoir else "Devoir", | |
| "note": note_float, | |
| } | |
| df = pd.concat([df, pd.DataFrame([new_row])], ignore_index=True) | |
| DATA_PATH.parent.mkdir(parents=True, exist_ok=True) | |
| df.to_csv(DATA_PATH, index=False) | |
| eleves = get_eleve_list(df) | |
| devoirs = get_devoir_list(df) | |
| msg = f"Note ajoutée pour {eleve} : {note_float} ({new_row['devoir']}, {d})." | |
| return msg, gr.update(choices=eleves, value=eleve), df, gr.update(choices=devoirs) | |
| def refresh_all(): | |
| """Recharge le CSV et met à jour tableau, élèves, devoirs.""" | |
| global df | |
| df = load_data() | |
| eleves = get_eleve_list(df) | |
| devoirs = get_devoir_list(df) | |
| return df, gr.update(choices=eleves), gr.update(choices=devoirs) | |
| def plot_student(eleve): | |
| """Retourne une figure matplotlib avec l'évolution des notes d'un élève.""" | |
| global df | |
| fig, ax = plt.subplots() | |
| if not eleve or df.empty: | |
| ax.set_title("Pas encore de notes") | |
| ax.set_xlabel("Date") | |
| ax.set_ylabel("Note") | |
| return fig | |
| sub = df[df["eleve"] == eleve].copy() | |
| if sub.empty: | |
| ax.set_title(f"Aucune note pour {eleve}") | |
| ax.set_xlabel("Date") | |
| ax.set_ylabel("Note") | |
| return fig | |
| # Conversion robuste de la colonne date | |
| if not np.issubdtype(sub["date"].dtype, np.datetime64): | |
| sub["date"] = pd.to_datetime( | |
| sub["date"], | |
| format="mixed", | |
| errors="coerce", | |
| ) | |
| sub = sub.dropna(subset=["date"]) | |
| sub = sub.sort_values("date") | |
| if sub.empty: | |
| ax.set_title(f"Aucune date valide pour {eleve}") | |
| ax.set_xlabel("Date") | |
| ax.set_ylabel("Note") | |
| return fig | |
| ax.plot(sub["date"], sub["note"], marker="o") | |
| ax.set_xlabel("Date") | |
| ax.set_ylabel("Note") | |
| ax.set_title(f"Évolution des notes - {eleve}") | |
| ax.set_ylim(0, 20) # si tu notes sur 20 | |
| fig.autofmt_xdate() | |
| return fig | |
| #def compute_stats(devoir): | |
| # """Calcule la moyenne et le classement pour un devoir.""" | |
| # global df | |
| # if not devoir or df.empty: | |
| # empty = pd.DataFrame(columns=["rang", "eleve", "note"]) | |
| # return "Pas de données pour ce devoir.", empty | |
| # sub = df[df["devoir"] == devoir].copy() | |
| # if sub.empty: | |
| # empty = pd.DataFrame(columns=["rang", "eleve", "note"]) | |
| # return f"Aucune note trouvée pour le devoir « {devoir} ».", empty | |
| # sub = sub.dropna(subset=["note", "eleve"]) | |
| # if sub.empty: | |
| # empty = pd.DataFrame(columns=["rang", "eleve", "note"]) | |
| # return f"Aucune note exploitable pour le devoir « {devoir} ».", empty | |
| # sub = sub.sort_values("note", ascending=False) | |
| # sub["rang"] = range(1, len(sub) + 1) | |
| # moyenne = sub["note"].mean() | |
| # resume = f"Moyenne pour « {devoir} » : {moyenne:.2f} / 20 ({len(sub)} élèves)." | |
| # classement = sub[["rang", "eleve", "note"]] | |
| # return resume, classement | |
| def compute_stats(devoir): | |
| """Calcule stats pour un devoir : moyenne, écart-type, classement, histogramme.""" | |
| global df | |
| # Figure par défaut pour l'histogramme | |
| fig, ax = plt.subplots() | |
| if not devoir or df.empty: | |
| stats = "Pas de données pour ce devoir." | |
| ax.set_title("Aucune note à afficher") | |
| ax.set_xlabel("Note") | |
| ax.set_ylabel("Nombre d'élèves") | |
| return stats, pd.DataFrame(), fig | |
| # ✅ ici on filtre sur la colonne 'devoir' | |
| sub = df[df["devoir"] == devoir].copy() | |
| if sub.empty: | |
| stats = "Pas de données pour ce devoir." | |
| ax.set_title("Aucune note à afficher") | |
| ax.set_xlabel("Note") | |
| ax.set_ylabel("Nombre d'élèves") | |
| return stats, pd.DataFrame(), fig | |
| # ✅ colonnes 'note' (minuscule) | |
| n = len(sub) | |
| mean = sub["note"].mean() | |
| std = sub["note"].std() | |
| min_v = sub["note"].min() | |
| max_v = sub["note"].max() | |
| stats = ( | |
| f"Devoir : {devoir}\n" | |
| f"Nombre d'élèves : {n}\n" | |
| f"Moyenne : {mean:.2f}\n" | |
| f"Écart-type : {std:.2f}\n" | |
| f"Minimum : {min_v:.2f}\n" | |
| f"Maximum : {max_v:.2f}") | |
| # ✅ classement : colonnes 'eleve' et 'note' | |
| classement = sub[["eleve", "note"]].copy() | |
| classement = classement.sort_values("note", ascending=False).reset_index(drop=True) | |
| classement.index += 1 # classement commence à 1 | |
| # ✅ histogramme sur 'note' | |
| ax.hist(sub["note"], bins=10, range=(0, 20), edgecolor="black") | |
| ax.set_title(f"Répartition des notes - {devoir}") | |
| ax.set_xlabel("Note") | |
| ax.set_ylabel("Nombre d'élèves") | |
| return stats, classement, fig | |
| def import_csv(file_obj): | |
| """Importe un CSV et remplace les données courantes.""" | |
| global df | |
| if file_obj is None: | |
| return "Aucun fichier sélectionné.", df, gr.update(), gr.update() | |
| try: | |
| df_new = pd.read_csv(file_obj.name) | |
| except Exception as e: | |
| return f"Erreur lors de la lecture du CSV : {e}", df, gr.update(), gr.update() | |
| # Normalisation des colonnes | |
| col_map = {} | |
| for c in df_new.columns: | |
| cl = c.lower() | |
| if cl in ("etudiant", "étudiant", "eleve", "élève"): | |
| col_map[c] = "eleve" | |
| elif cl in ("matiere", "matière", "devoir"): | |
| col_map[c] = "devoir" | |
| elif cl == "note": | |
| col_map[c] = "note" | |
| elif cl == "date": | |
| col_map[c] = "date" | |
| if col_map: | |
| df_new = df_new.rename(columns=col_map) | |
| required = {"eleve", "date", "devoir", "note"} | |
| if not required.issubset(df_new.columns): | |
| msg = "Le fichier CSV doit contenir les colonnes : eleve, date, devoir, note." | |
| return msg, df, gr.update(), gr.update() | |
| df_new = df_new[["eleve", "date", "devoir", "note"]] | |
| df = df_new.reset_index(drop=True) | |
| DATA_PATH.parent.mkdir(parents=True, exist_ok=True) | |
| df.to_csv(DATA_PATH, index=False) | |
| eleves = get_eleve_list(df) | |
| devoirs = get_devoir_list(df) | |
| status = f"Fichier importé avec succès ({len(df)} lignes)." | |
| return status, df, gr.update(choices=eleves), gr.update(choices=devoirs) | |
| def get_csv_file(): | |
| """Retourne le chemin vers le CSV pour téléchargement.""" | |
| global df | |
| DATA_PATH.parent.mkdir(parents=True, exist_ok=True) | |
| df.to_csv(DATA_PATH, index=False) | |
| return str(DATA_PATH) | |
| with gr.Blocks(title="Suivi des notes") as demo: | |
| gr.Markdown( | |
| """ | |
| #Suivi des notes des élèves. | |
| Bienvenue :) | |
| """ | |
| ) | |
| # ---------- Onglet 1 : Ajouter une note ---------- | |
| with gr.Tab("Ajouter une note"): | |
| with gr.Row(): | |
| eleve_input = gr.Textbox( | |
| label="Nom de l'élève", | |
| ) | |
| devoir_input = gr.Textbox( | |
| label="Devoir / Matière", | |
| ) | |
| with gr.Row(): | |
| note_input = gr.Textbox( | |
| label="Note", | |
| ) | |
| date_input = gr.Textbox( | |
| label="Date (optionnel, AAAA-MM-JJ)", | |
| ) | |
| add_button = gr.Button("Ajouter la note ✅") | |
| status_output = gr.Textbox(label="Statut", interactive=False) | |
| with gr.Tab("Visualiser les notes"): | |
| refresh_button = gr.Button("🔄 Recharger les données") | |
| eleve_dropdown = gr.Dropdown( | |
| label="Choisir un élève", | |
| choices=get_eleve_list(df),) | |
| with gr.Row(): | |
| line_plot = gr.Plot(label="Évolution des notes") | |
| table = gr.Dataframe( | |
| label="Toutes les notes", | |
| value=df, | |
| interactive=False, | |
| ) | |
| # --- Télécharger le graphique --- | |
| download_plot_button = gr.Button("📥 Télécharger le graphique") | |
| download_plot_file = gr.File(label="Télécharger le PNG") | |
| download_plot_button.click( | |
| fn=download_plot, | |
| inputs=eleve_dropdown, | |
| outputs=download_plot_file, | |
| ) | |
| # ---------- Onglet 2 : Visualiser ---------- | |
| #with gr.Tab("Visualiser les notes"): | |
| # refresh_button = gr.Button("🔄 Recharger les données") | |
| # eleve_dropdown = gr.Dropdown( | |
| # label="Choisir un élève", | |
| # choices=get_eleve_list(df), | |
| # ) | |
| # with gr.Row(): | |
| # line_plot = gr.Plot(label="Évolution des notes") | |
| # table = gr.Dataframe( | |
| # label="Toutes les notes", | |
| # value=df, | |
| # interactive=False, | |
| # ) | |
| # ---------- Onglet 3 : Statistiques ---------- | |
| with gr.Tab("Statistiques par devoir"): | |
| devoir_dropdown = gr.Dropdown( | |
| label="Choisir un devoir", | |
| choices=get_devoir_list(df), | |
| ) | |
| stats_text = gr.Textbox( | |
| label="Moyenne et informations", | |
| interactive=False, | |
| ) | |
| classement_table = gr.Dataframe( | |
| label="Classement sur ce devoir", | |
| interactive=False, | |
| ) | |
| hist_plot = gr.Plot( | |
| label="Répartition des notes (histogramme)", | |
| ) | |
| # ---------- Onglet 4 : Données ---------- | |
| with gr.Tab("Données (import / export)"): | |
| gr.Markdown( | |
| "### 📂 Importer / exporter les notes\n" | |
| "- Importer un fichier CSV existant pour charger des notes\n" | |
| "- Exporter le fichier CSV courant pour le sauvegarder sur votre ordinateur" | |
| ) | |
| csv_upload = gr.File(label="Importer un fichier CSV", file_types=[".csv"]) | |
| import_button = gr.Button("Charger ce CSV") | |
| import_status = gr.Textbox(label="Statut de l'import", interactive=False) | |
| export_button = gr.Button("Préparer le fichier CSV pour téléchargement") | |
| download_file = gr.File(label="Télécharger grades.csv") | |
| # ---------- Callbacks ---------- | |
| add_button.click( | |
| fn=add_grade, | |
| inputs=[eleve_input, devoir_input, note_input, date_input], | |
| outputs=[status_output, eleve_dropdown, table, devoir_dropdown], | |
| ) | |
| refresh_button.click( | |
| fn=refresh_all, | |
| inputs=None, | |
| outputs=[table, eleve_dropdown, devoir_dropdown], | |
| ) | |
| eleve_dropdown.change( | |
| fn=plot_student, | |
| inputs=eleve_dropdown, | |
| outputs=line_plot, | |
| ) | |
| devoir_dropdown.change(fn=compute_stats, | |
| inputs=devoir_dropdown, | |
| outputs=[stats_text, classement_table, hist_plot], | |
| ) | |
| import_button.click( | |
| fn=import_csv, | |
| inputs=csv_upload, | |
| outputs=[import_status, table, eleve_dropdown, devoir_dropdown], | |
| ) | |
| export_button.click( | |
| fn=get_csv_file, | |
| inputs=None, | |
| outputs=download_file, | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() | |