diff --git a/app/but/jury_but_recap.py b/app/but/jury_but_recap.py
index 38e1fd68d8d389cf110156dd6bf51171eb15aed3..a0a74b313462d8442763c25dbf91e868983c0f87 100644
--- a/app/but/jury_but_recap.py
+++ b/app/but/jury_but_recap.py
@@ -34,12 +34,17 @@ from app.scodoc.sco_exceptions import ScoValueError
 
 
 def formsemestre_saisie_jury_but(
-    formsemestre2: FormSemestre, readonly: bool = False, selected_etudid: int = None
+    formsemestre2: FormSemestre,
+    readonly: bool = False,
+    selected_etudid: int = None,
+    mode="jury",
 ) -> str:
     """formsemestre est un semestre PAIR
     Si readonly, ne montre pas le lien "saisir la décision"
 
     => page html complète
+
+    Si mode == "recap", table recap des codes, sans liens de saisie.
     """
     # Quick & Dirty
     # pour chaque etud de res2 trié
@@ -55,7 +60,9 @@ def formsemestre_saisie_jury_but(
     if formsemestre2.semestre_id % 2 != 0:
         raise ScoValueError("Cette page ne fonctionne que sur les semestres pairs")
 
-    rows, titles, column_ids = get_table_jury_but(formsemestre2, readonly=readonly)
+    rows, titles, column_ids = get_table_jury_but(
+        formsemestre2, readonly=readonly, mode=mode
+    )
     if not rows:
         return (
             '<div class="table_recap"><div class="message">aucun étudiant !</div></div>'
@@ -63,8 +70,9 @@ def formsemestre_saisie_jury_but(
     filename = scu.sanitize_filename(
         f"""jury-but-{formsemestre2.titre_num()}-{time.strftime("%Y-%m-%d")}"""
     )
+    klass = "table_jury_but_bilan" if mode == "recap" else ""
     table_html = build_table_jury_but_html(
-        filename, rows, titles, column_ids, selected_etudid=selected_etudid
+        filename, rows, titles, column_ids, selected_etudid=selected_etudid, klass=klass
     )
     H = [
         html_sco_header.sco_header(
@@ -77,16 +85,45 @@ def formsemestre_saisie_jury_but(
             formsemestre_id=formsemestre2.id
         ),
     ]
+    if mode == "recap":
+        H.append(
+            """<h3>Décisions de jury enregistrées pour les étudiants de ce semestre</h3>"""
+        )
     H.append(
         f"""
 
     {table_html}
 
     <div class="table_jury_but_links">
-    <p><a class="sdtlink" href="{url_for(
+    """
+    )
+
+    if (mode == "recap") and not readonly:
+        H.append(
+            f"""
+    <p><a class="stdlink" href="{url_for(
+        "notes.formsemestre_saisie_jury",
+        scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre2.id)
+    }">Saisie des décisions du jury</a>
+    </p>"""
+        )
+    else:
+        H.append(
+            f"""
+    <p><a class="stdlink" href="{url_for(
         "notes.formsemestre_validation_auto_but",
         scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre2.id)
-    }">Calcul automatique des décisions du jury</a></p>
+    }">Calcul automatique des décisions du jury</a>
+    </p>
+    <p><a class="stdlink" href="{url_for(
+        "notes.formsemestre_jury_but_recap",
+        scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre2.id)
+    }">Tableau récapitulatif des décisions du jury</a>
+    </p>
+    """
+        )
+    H.append(
+        f"""
     </div>
 
     {html_sco_header.sco_footer()}
@@ -96,12 +133,12 @@ def formsemestre_saisie_jury_but(
 
 
 def build_table_jury_but_html(
-    filename: str, rows, titles, column_ids, selected_etudid: int = None
+    filename: str, rows, titles, column_ids, selected_etudid: int = None, klass=""
 ) -> str:
     """assemble la table html"""
-    footer_rows = []  # inutile pour l'instant, à voir XXX
+    footer_rows = []  # inutilisé pour l'instant
     H = [
-        f"""<div class="table_recap"><table class="table_recap apc jury table_jury_but"
+        f"""<div class="table_recap"><table class="table_recap apc jury table_jury_but {klass}"
             data-filename="{filename}">"""
     ]
     # header
@@ -274,7 +311,7 @@ class RowCollector:
 
 
 def get_table_jury_but(
-    formsemestre2: FormSemestre, readonly: bool = False
+    formsemestre2: FormSemestre, readonly: bool = False, mode="jury"
 ) -> tuple[list[dict], list[str], list[str]]:
     """Construit la table des résultats annuels pour le jury BUT"""
     res2: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre2)
@@ -302,7 +339,7 @@ def get_table_jury_but(
             "col_code_annee",
         )
         # --- Le lien de saisie
-        if not readonly:
+        if not readonly and not mode == "recap":
             row.add_cell(
                 "lien_saisie",
                 "",
@@ -316,6 +353,7 @@ def get_table_jury_but(
                     {"modif." if deca.code_valide else "saisie"}
                     décision</a>
                 """,
+                "col_lien_saisie_but",
             )
         rows.append(row.row)
     if len(rows) > 0:
diff --git a/app/static/js/table_recap.js b/app/static/js/table_recap.js
index 18829d8edb36ea3fe52b5e87a4ea7d0cf25beb05..8d5264412427312b42cfb7759bd5e0c67ec867fd 100644
--- a/app/static/js/table_recap.js
+++ b/app/static/js/table_recap.js
@@ -1,6 +1,14 @@
 // Tableau recap notes
 $(function () {
     $(function () {
+        let hidden_colums = ["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
+            hidden_colums = hidden_colums.concat(["col_ue", "col_rcue", "col_lien_saisie_but"]);
+        } else {
+            hidden_colums = hidden_colums.concat(["recorded_code"]);
+        }
         // Les boutons dépendent du mode BUT ou classique:
         let buttons = [
             {
@@ -125,7 +133,7 @@ $(function () {
                 "columnDefs": [
                     {
                         // cache les codes, le détail de l'identité, les groupes, les colonnes admission et les vides
-                        targets: ["codes", "identite_detail", "partition_aux", "partition_rangs", "admission", "col_empty", "recorded_code"],
+                        targets: hidden_colums,
                         visible: false,
                     },
                     {
@@ -175,10 +183,13 @@ $(function () {
             $(this).addClass('selected');
         }
     });
-    // Pour montrer et highlihter l'étudiant sélectionné:
+    // Pour montrer et surligner l'étudiant sélectionné:
     $(function () {
-        document.querySelector("#row_selected").scrollIntoView();
-        window.scrollBy(0, -50);
-        document.querySelector("#row_selected").classList.add("selected");
+        let row_selected = document.querySelector("#row_selected");
+        if (row_selected) {
+            row_selected.scrollIntoView();
+            window.scrollBy(0, -50);
+            row_selected.classList.add("selected");
+        }
     });
 });
diff --git a/app/views/notes.py b/app/views/notes.py
index f00758b606634144c164ae94a600304822455fbf..97aa43bafe7f5905a76e74eef0a4b30e19285dc8 100644
--- a/app/views/notes.py
+++ b/app/views/notes.py
@@ -2613,6 +2613,23 @@ def formsemestre_saisie_jury(formsemestre_id: int, selected_etudid: int = None):
     )
 
 
+@bp.route("/formsemestre_jury_but_recap")
+@scodoc
+@permission_required(Permission.ScoView)
+@scodoc7func
+def formsemestre_jury_but_recap(formsemestre_id: int, selected_etudid: int = None):
+    """Tableau affichage des codes"""
+    readonly = not sco_permissions_check.can_validate_sem(formsemestre_id)
+    formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    if not (formsemestre.formation.is_apc() and formsemestre.semestre_id % 2 == 0):
+        raise ScoValueError(
+            "formsemestre_jury_but_recap: réservé aux semestres pairs de BUT"
+        )
+    return jury_but_recap.formsemestre_saisie_jury_but(
+        formsemestre, readonly=readonly, selected_etudid=selected_etudid, mode="recap"
+    )
+
+
 sco_publish(
     "/formsemestre_lettres_individuelles",
     sco_pvjury.formsemestre_lettres_individuelles,