diff --git a/app/comp/moy_mod.py b/app/comp/moy_mod.py
index a75055b1..95524597 100644
--- a/app/comp/moy_mod.py
+++ b/app/comp/moy_mod.py
@@ -69,6 +69,8 @@ class ModuleImplResults:
         "nombre d'inscrits (non DEM) au module"
         self.evaluations_completes = []
         "séquence de booléens, indiquant les évals à prendre en compte."
+        self.evaluations_completes_dict = {}
+        "{ evaluation.id : bool } indique si à prendre en compte ou non."
         self.evaluations_etat = {}
         "{ evaluation_id: EvaluationEtat }"
         #
@@ -122,6 +124,7 @@ class ModuleImplResults:
         # dataFrame vide, index = tous les inscrits au SEMESTRE
         evals_notes = pd.DataFrame(index=self.etudids, dtype=float)
         self.evaluations_completes = []
+        self.evaluations_completes_dict = {}
         for evaluation in moduleimpl.evaluations:
             eval_df = self._load_evaluation_notes(evaluation)
             # is_complete ssi tous les inscrits (non dem) au semestre ont une note
@@ -131,6 +134,7 @@ class ModuleImplResults:
                 == self.nb_inscrits_module
             ) or evaluation.publish_incomplete  # immédiate
             self.evaluations_completes.append(is_complete)
+            self.evaluations_completes_dict[evaluation.id] = is_complete
 
             # NULL en base => ABS (= -999)
             eval_df.fillna(scu.NOTES_ABSENCE, inplace=True)
diff --git a/app/comp/res_classic.py b/app/comp/res_classic.py
index b203ec58..6cf092a8 100644
--- a/app/comp/res_classic.py
+++ b/app/comp/res_classic.py
@@ -60,6 +60,25 @@ class ResultatsSemestreClassic(NotesTableCompat):
         """
         return self.modimpls_results[moduleimpl_id].etuds_moy_module.get(etudid, "NI")
 
+    def get_mod_stats(self, moduleimpl_id: int) -> dict:
+        """Stats sur les notes obtenues dans un modimpl"""
+        notes_series: pd.Series = self.modimpls_results[moduleimpl_id].etuds_moy_module
+        nb_notes = len(notes_series)
+        if not nb_notes:
+            super().get_mod_stats(moduleimpl_id)
+        return {
+            # Series: Statistical methods from ndarray have been overridden to automatically
+            # exclude missing data (currently represented as NaN)
+            "moy": notes_series.mean(),  # donc sans prendre en compte les NaN
+            "max": notes_series.max(),
+            "min": notes_series.min(),
+            "nb_notes": nb_notes,
+            "nb_missing": sum(notes_series.isna()),
+            "nb_valid_evals": sum(
+                self.modimpls_results[moduleimpl_id].evaluations_completes
+            ),
+        }
+
 
 def notes_sem_load_matrix(formsemestre: FormSemestre) -> tuple:
     """Calcule la matrice des notes du semestre
diff --git a/app/comp/res_sem.py b/app/comp/res_sem.py
index 5eccd325..cb9290b4 100644
--- a/app/comp/res_sem.py
+++ b/app/comp/res_sem.py
@@ -9,6 +9,7 @@ from functools import cached_property
 import numpy as np
 import pandas as pd
 from app.comp.aux import StatsMoyenne
+from app.comp.moy_mod import ModuleImplResults
 from app.models import FormSemestre, ModuleImpl
 from app.scodoc import sco_utils as scu
 from app.scodoc.sco_cache import ResultatsSemestreCache
@@ -36,11 +37,12 @@ class ResultatsSemestre:
         self.formsemestre = formsemestre
         # BUT ou standard ? (apc == "approche par compétences")
         self.is_apc = formsemestre.formation.is_apc()
-        # Attributs "virtuels", définis pas les sous-classes
+        # Attributs "virtuels", définis dans les sous-classes
         #  ResultatsSemestreBUT ou ResultatsSemestreStd
         self.etud_moy_ue = {}
         self.etud_moy_gen = {}
         self.etud_moy_gen_ranks = {}
+        self.modimpls_results: ModuleImplResults = None
         # TODO
 
     def load_cached(self) -> bool:
@@ -215,7 +217,7 @@ class NotesTableCompat(ResultatsSemestre):
 
     def get_etud_moy_gen(self, etudid):  # -> float | str
         """Moyenne générale de cet etudiant dans ce semestre.
-        Prend en compte les UE capitalisées. (TODO)
+        Prend(ra) en compte les UE capitalisées. (TODO) XXX
         Si apc, moyenne indicative.
         Si pas de notes: 'NA'
         """
@@ -241,30 +243,37 @@ class NotesTableCompat(ResultatsSemestre):
         return (None, 0)  # XXX unimplemented TODO
 
     def get_evals_in_mod(self, moduleimpl_id: int) -> list[dict]:
-        "liste des évaluations valides dans un module"
+        """Liste d'informations (compat NotesTable) sur évaluations completes
+        de ce module.
+        Évaluation "complete" ssi toutes notes saisies ou en attente.
+        """
         modimpl = ModuleImpl.query.get(moduleimpl_id)
         evals_results = []
         for e in modimpl.evaluations:
-            d = e.to_dict()
-            d["heure_debut"] = e.heure_debut  # datetime.time
-            d["heure_fin"] = e.heure_fin
-            d["jour"] = e.jour  # datetime
-            d["notes"] = {
-                etud.id: {
-                    "etudid": etud.id,
-                    "value": self.results.modimpls_evals_notes[e.moduleimpl_id][e.id][
-                        etud.id
-                    ],
+            if self.modimpls_results[moduleimpl_id].evaluations_completes_dict[e.id]:
+                d = e.to_dict()
+                d["heure_debut"] = e.heure_debut  # datetime.time
+                d["heure_fin"] = e.heure_fin
+                d["jour"] = e.jour  # datetime
+                d["notes"] = {
+                    etud.id: {
+                        "etudid": etud.id,
+                        "value": self.modimpls_evals_notes[e.moduleimpl_id][e.id][
+                            etud.id
+                        ],
+                    }
+                    for etud in self.etuds
                 }
-                for etud in self.results.etuds
-            }
-            evals_results.append(d)
+                evals_results.append(d)
         return evals_results
 
     def get_moduleimpls_attente(self):
         return []  # XXX TODO
 
-    def get_mod_stats(self, moduleimpl_id):
+    def get_mod_stats(self, moduleimpl_id: int) -> dict:
+        """Stats sur les notes obtenues dans un modimpl
+        Vide en APC
+        """
         return {
             "moy": "-",
             "max": "-",
diff --git a/app/scodoc/notes_table.py b/app/scodoc/notes_table.py
index 828ea228..0e21efb2 100644
--- a/app/scodoc/notes_table.py
+++ b/app/scodoc/notes_table.py
@@ -170,6 +170,7 @@ class NotesTable:
     """
 
     def __init__(self, formsemestre_id):
+        # XXX breakpoint()
         log(f"NotesTable( formsemestre_id={formsemestre_id} )")
         if not formsemestre_id:
             raise ValueError("invalid formsemestre_id (%s)" % formsemestre_id)
diff --git a/app/scodoc/sco_recapcomplet.py b/app/scodoc/sco_recapcomplet.py
index 6c331146..293dd208 100644
--- a/app/scodoc/sco_recapcomplet.py
+++ b/app/scodoc/sco_recapcomplet.py
@@ -303,9 +303,11 @@ def make_formsemestre_recapcomplet(
     sem = sco_formsemestre.do_formsemestre_list(
         args={"formsemestre_id": formsemestre_id}
     )[0]
-    nt = sco_cache.NotesTableCache.get(formsemestre_id)
+    parcours = formsemestre.formation.get_parcours()
+
+    # nt = sco_cache.NotesTableCache.get(formsemestre_id)
     # XXX EXPERIMENTAL
-    # nt = ResultatsSemestreClassic(formsemestre)
+    nt = ResultatsSemestreClassic(formsemestre)
     modimpls = nt.get_modimpls_dict()
     ues = nt.get_ues_stat_dict()  # incluant le(s) UE de sport
     #
@@ -701,9 +703,7 @@ def make_formsemestre_recapcomplet(
             idx_col_moy = idx_col_gr + 1
             cssclass = "recap_col_moy"
             try:
-                if float(nsn[idx_col_moy]) < (
-                    nt.parcours.BARRE_MOY - scu.NOTES_TOLERANCE
-                ):
+                if float(nsn[idx_col_moy]) < (parcours.BARRE_MOY - scu.NOTES_TOLERANCE):
                     cssclass = "recap_col_moy_inf"
             except:
                 pass
@@ -718,11 +718,11 @@ def make_formsemestre_recapcomplet(
 
                     if (ir < (nblines - 4)) or (ir == nblines - 3):
                         try:
-                            if float(nsn[i]) < nt.parcours.get_barre_ue(
+                            if float(nsn[i]) < parcours.get_barre_ue(
                                 ue["type"]
                             ):  # NOTES_BARRE_UE
                                 cssclass = "recap_col_ue_inf"
-                            elif float(nsn[i]) >= nt.parcours.NOTES_BARRE_VALID_UE:
+                            elif float(nsn[i]) >= parcours.NOTES_BARRE_VALID_UE:
                                 cssclass = "recap_col_ue_val"
                         except:
                             pass
@@ -732,7 +732,7 @@ def make_formsemestre_recapcomplet(
                         ir == nblines - 3
                     ):  # si moyenne generale module < barre ue, surligne:
                         try:
-                            if float(nsn[i]) < nt.parcours.get_barre_ue(ue["type"]):
+                            if float(nsn[i]) < parcours.get_barre_ue(ue["type"]):
                                 cssclass = "recap_col_moy_inf"
                         except:
                             pass