diff --git a/app/comp/res_but.py b/app/comp/res_but.py
index f3f0c97dbd2a424c44881edd3b412eb0573d4602..831c0104fafb5f51358af1dd296ee76db202bca5 100644
--- a/app/comp/res_but.py
+++ b/app/comp/res_but.py
@@ -17,8 +17,9 @@ from app.comp.bonus_spo import BonusSport
 from app.models import ScoDocSiteConfig
 from app.models.moduleimpls import ModuleImpl
 from app.models.ues import DispenseUE, UniteEns
-from app.scodoc.sco_codes_parcours import UE_SPORT
 from app.scodoc import sco_preferences
+from app.scodoc.sco_codes_parcours import UE_SPORT
+from app.scodoc.sco_utils import ModuleType
 
 
 class ResultatsSemestreBUT(NotesTableCompat):
@@ -185,9 +186,15 @@ class ResultatsSemestreBUT(NotesTableCompat):
         modimpls = [
             modimpl
             for modimpl in self.formsemestre.modimpls_sorted
-            if modimpl.module.ue.type != UE_SPORT
-            and (coefs[modimpl.id][ue.id] != 0)
-            and self.modimpl_inscr_df[modimpl.id][etudid]
+            if (
+                modimpl.module.ue.type != UE_SPORT
+                and (coefs[modimpl.id][ue.id] != 0)
+                and self.modimpl_inscr_df[modimpl.id][etudid]
+            )
+            or (
+                modimpl.module.module_type == ModuleType.MALUS
+                and modimpl.module.ue_id == ue.id
+            )
         ]
         if not with_bonus:
             return [
diff --git a/app/comp/res_common.py b/app/comp/res_common.py
index e8b1c893be486db4d877246b42cdec8a43bdbc15..bbb14ca342e8511411066642d5a5a18f932a8a92 100644
--- a/app/comp/res_common.py
+++ b/app/comp/res_common.py
@@ -667,7 +667,7 @@ class ResultatsSemestre(ResultatsCache):
             "ues_validables",
             "UEs",
             ue_valid_txt_html,
-            "col_ues_validables",
+            group="col_ues_validables",
             classes=classes,
             raw_content=ue_valid_txt,
             data={"order": row.nb_ues_validables},  #  tri
@@ -719,7 +719,7 @@ class ResultatsSemestre(ResultatsCache):
             ue.acronyme,
             table.fmt_note(val),
             group=f"col_ue_{ue.id}",
-            classes=["col_ue", note_class],
+            classes=["col_ue", "col_moy_ue", note_class],
         )
         row.table.foot_title_row.cells[col_id].target_attrs[
             "title"
@@ -751,7 +751,11 @@ class ResultatsSemestre(ResultatsCache):
             col_id = f"moy_{modimpl.module.type_abbrv()}_{modimpl.id}_{ue.id}"
             val_fmt = val_fmt_html = table.fmt_note(val)
             if modimpl.module.module_type == scu.ModuleType.MALUS:
-                val_fmt_html = (scu.EMO_RED_TRIANGLE_DOWN + val_fmt) if val else ""
+                val_fmt_html = (
+                    (scu.EMO_RED_TRIANGLE_DOWN + val_fmt)
+                    if val and not np.isnan(val)
+                    else ""
+                )
             cell = row.add_cell(
                 col_id,
                 modimpl.module.code,
@@ -992,22 +996,7 @@ class ResultatsSemestre(ResultatsCache):
         )
         first_partition = True
         for partition in partitions:
-            col_classes = []  # la classe "partition" sera ajoutée par la table
-            if not first_partition:
-                col_classes.append("partition_aux")
-            first_partition = False
             cid = f"part_{partition['partition_id']}"
-            cell_head, cell_foot = table.add_title(cid, partition["partition_name"])
-            cell_head.classes += col_classes
-            cell_foot.classes += col_classes
-
-            if partition["bul_show_rank"]:
-                rg_cid = cid + "_rg"  # rang dans la partition
-                cell_head, cell_foot = table.add_title(
-                    cid, f"Rg {partition['partition_name']}"
-                )
-                cell_head.classes.append("partition_rangs")
-                cell_foot.classes.append("partition_rangs")
 
             partition_etud_groups = partitions_etud_groups[partition["partition_id"]]
             for row in table.rows:
@@ -1030,9 +1019,12 @@ class ResultatsSemestre(ResultatsCache):
                         cid,
                         partition["partition_name"],
                         gr_name,
-                        "partition",
-                        classes=col_classes,
+                        group="partition",
+                        classes=[] if first_partition else ["partition_aux"],
+                        # la classe "partition" est ajoutée par la Table car c'est le group
+                        # la classe "partition_aux" est ajoutée à partir de la 2eme partition affichée
                     )
+                    first_partition = False
 
                 # Rangs dans groupe
                 if (
@@ -1041,7 +1033,14 @@ class ResultatsSemestre(ResultatsCache):
                     and (group["id"] in self.moy_gen_rangs_by_group)
                 ):
                     rang = self.moy_gen_rangs_by_group[group["id"]][0]
-                    row.add_cell(rg_cid, None, rang.get(etudid, ""), "partition")
+                    rg_cid = cid + "_rg"  # rang dans la partition
+                    row.add_cell(
+                        rg_cid,
+                        f"Rg {partition['partition_name']}",
+                        rang.get(etudid, ""),
+                        group="partition",
+                        classes=["partition_aux"],
+                    )
 
     def _recap_add_evaluations(self, table: tb.Table):
         """Ajoute les colonnes avec les notes aux évaluations
diff --git a/app/models/notes.py b/app/models/notes.py
index 8d152a588c135d37cbee3e1dac0d4b87851899d9..74bf2f183705b3c309b2c611b156020dfa284181 100644
--- a/app/models/notes.py
+++ b/app/models/notes.py
@@ -4,8 +4,6 @@
 """
 
 from app import db
-
-import app.scodoc.notesdb as ndb
 import app.scodoc.sco_utils as scu
 
 
@@ -53,6 +51,13 @@ class NotesNotes(db.Model):
         d.pop("_sa_instance_state", None)
         return d
 
+    def __repr__(self):
+        "pour debug"
+        from app.models.evaluations import Evaluation
+
+        return f"""<{self.__class__.__name__} {self.id} v={self.value} {self.date.isoformat()
+            } {Evaluation.query.get(self.evaluation_id) if self.evaluation_id else "X" }>"""
+
 
 class NotesNotesLog(db.Model):
     """Historique des modifs sur notes (anciennes entrees de notes_notes)"""
diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py
index a3959006e7afecea2dfe54bb489ec71500c67da6..5902dbf73387c41334747d89af2260354eca6258 100644
--- a/app/scodoc/sco_groups.py
+++ b/app/scodoc/sco_groups.py
@@ -1223,6 +1223,7 @@ def partition_move(partition_id, after=0, redirect=1):
             partition["numero"], neigh["numero"] = neigh["numero"], partition["numero"]
             partitionEditor.edit(cnx, partition)
             partitionEditor.edit(cnx, neigh)
+            sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
 
     # redirect to partition edit page:
     if redirect:
@@ -1297,7 +1298,7 @@ def partition_set_name(partition_id, partition_name, redirect=1):
     )
     if len(r) > 1 or (len(r) == 1 and r[0]["id"] != partition_id):
         raise ScoValueError(
-            "Partition %s déjà existante dans ce semestre !" % partition_name
+            f"Partition {partition_name} déjà existante dans ce semestre !"
         )
 
     if not sco_permissions_check.can_change_groups(formsemestre_id):
@@ -1307,6 +1308,7 @@ def partition_set_name(partition_id, partition_name, redirect=1):
     partitionEditor.edit(
         cnx, {"partition_id": partition_id, "partition_name": partition_name}
     )
+    sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
 
     # redirect to partition edit page:
     if redirect:
@@ -1339,6 +1341,7 @@ def group_set_name(group: GroupDescr, group_name: str, redirect=True):
     group.group_name = group_name
     db.session.add(group)
     db.session.commit()
+    sco_cache.invalidate_formsemestre(formsemestre_id=group.partition.formsemestre_id)
 
     # redirect to partition edit page:
     if redirect:
@@ -1396,8 +1399,6 @@ def groups_auto_repartition(partition_id=None):
     """Reparti les etudiants dans des groupes dans une partition, en respectant le niveau
     et la mixité.
     """
-    from app.scodoc import sco_formsemestre
-
     partition = get_partition(partition_id)
     if not partition["groups_editable"]:
         raise AccessDenied("Partition non éditable")
diff --git a/app/scodoc/table_builder.py b/app/scodoc/table_builder.py
index 8ddd70c8920947da7675744f88a68fbd7d631355..d7fc27e5389c993ec9f7faa0883668d497b22cd4 100644
--- a/app/scodoc/table_builder.py
+++ b/app/scodoc/table_builder.py
@@ -227,7 +227,11 @@ class Table(Element):
         if col_id not in self.titles:
             self.titles[col_id] = title
             self.head_title_row.cells[col_id] = self.head_title_row.add_cell(
-                col_id, None, title, classes=classes
+                col_id,
+                None,
+                title,
+                classes=classes,
+                group=self.column_group.get(col_id),
             )
             self.foot_title_row.cells[col_id] = self.foot_title_row.add_cell(
                 col_id, None, title, classes=classes
@@ -276,9 +280,14 @@ class Row(Element):
         group: groupe de colonnes
         classes is a list of css class names
         """
+        if (classes is None) or (group not in classes):
+            # ajoute le nom de groupe aux classes
+            classes = [group or ""] + (classes or [])
+        else:
+            classes = classes.copy()
         cell = Cell(
             content,
-            (classes or []) + [group or ""],  # ajoute le nom de groupe aux classes
+            classes,
             elt=elt or self.cell_elt,
             attrs=attrs,
             data=data,
@@ -294,7 +303,7 @@ class Row(Element):
         """Add a cell to the row.
         Si title est None, il doit avoir été ajouté avec table.add_title().
         """
-        cell.data["group"] = column_group
+        cell.data["group"] = column_group or ""
         self.cells[col_id] = cell
         if col_id not in self.table.column_ids:
             self.table.column_ids.append(col_id)
@@ -375,7 +384,7 @@ class Cell(Element):
         if self.elt == "th":
             self.attrs["scope"] = "row"
 
-        self.data = data or {}
+        self.data = data.copy() if data else {}
         self.raw_content = raw_content or content
         self.target = target
         self.target_attrs = target_attrs or {}
diff --git a/app/static/js/table_recap.js b/app/static/js/table_recap.js
index 7431ae5a162a6dfd24cce8cc9da7546a61a247eb..62080b4b327c8ecd7db304bbf3c606cab0ea37be 100644
--- a/app/static/js/table_recap.js
+++ b/app/static/js/table_recap.js
@@ -1,7 +1,11 @@
 // Tableau recap notes
 $(function () {
     $(function () {
-        let hidden_colums = ["etud_codes", "identite_detail", "partition_aux", "partition_rangs", "admission", "col_empty"];
+        let hidden_colums = [
+            "etud_codes", "identite_detail",
+            "partition_aux", "partition_rangs", "admission",
+            "col_empty"
+        ];
         let mode_jury_but_bilan = $('table.table_recap').hasClass("table_jury_but_bilan");
         if (mode_jury_but_bilan) {
             // table bilan décisions: cache les notes
@@ -30,6 +34,7 @@ $(function () {
 
         // Les colonnes visibles étant mémorisé, il faut initialiser les titres des boutons
         function update_buttons_labels(dt) {
+            console.log("update_buttons_labels");
             dt.buttons('toggle_ident:name').text(dt.columns(".identite_detail").visible()[0] ? "Nom seul" : "Civ/Nom/Prénom");
             dt.buttons('toggle_partitions:name').text(dt.columns(".partition_aux").visible()[0] ? "Cacher les groupes" : "Montrer groupes");
             if (!$('table.table_recap').hasClass("table_jury_but")) {
@@ -106,7 +111,7 @@ $(function () {
                 $('table.table_recap').hasClass("apc") ?
                     {
                         name: "toggle_res",
-                        text: "Visilité ressources",
+                        text: "Visibilité ressources",
                         action: function (e, dt, node, config) {
                             let visible = dt.columns(".col_res").visible()[0];
                             dt.columns(".col_res").visible(!visible);
@@ -159,6 +164,14 @@ $(function () {
                 }
             });
         }
+        buttons.push({
+            name: "reset_table_display",
+            text: "Rétablir affichage par défaut",
+            action: function (e, dt, node, config) {
+                localStorage.clear();
+                location.reload();
+            }
+        });
         try {
             let table = $('table.table_recap').DataTable(
                 {
@@ -182,7 +195,7 @@ $(function () {
                         },
                         {
                             // Elimine les 0 à gauche pour les exports excel et les "copy"
-                            targets: ["col_mod", "col_moy_gen", "col_ue", "col_res", "col_sae", "evaluation", "col_rcue"],
+                            targets: ["col_mod", "col_moy_gen", "col_moy_ue", "col_res", "col_sae", "evaluation", "col_rcue"],
                             render: function (data, type, row) {
                                 return type === 'export' ? data.replace(/0(\d\..*)/, '$1') : data;
                             }
@@ -194,6 +207,14 @@ $(function () {
                                 return type === 'export' ? data.replace(/.*(\d\d\.\d\d)/, '$1').replace(/0(\d\..*)/, '$1') : data;
                             }
                         },
+                        {
+                            // Elimine emoji warning sur UEs
+                            targets: ["col_ues_validables"],
+                            render: function (data, type, row) {
+                                return type === 'export' ? data.replace(/(\d+\/\d+).*/, '$1') : data;
+                            }
+                        }
+
                     ],
                     dom: 'Bfrtip',
                     buttons: [
@@ -235,6 +256,7 @@ $(function () {
                     "order": order_info,
                 }
             );
+            update_buttons_labels(table);
         } catch (error) {
             // l'erreur peut etre causee par un ancien storage:
             localStorage.removeItem(etudids_key);
@@ -242,7 +264,6 @@ $(function () {
             localStorage.removeItem(order_info_key);
             location.reload();
         }
-        update_buttons_labels(table);
     });
     $('table.table_recap tbody').on('click', 'tr', function () {
         if ($(this).hasClass('selected')) {