diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py
index 39c657aa9c043de9c85365ef26dcb56ea3e375f2..738a474424da2977c170aa5179e07240dd82695c 100644
--- a/app/models/formsemestre.py
+++ b/app/models/formsemestre.py
@@ -973,7 +973,7 @@ class FormSemestre(models.ScoDocModel):
 
     def etudids_actifs(self) -> tuple[list[int], set[int]]:
         """Liste les etudids inscrits (incluant DEM et DEF),
-        qui ser al'index des dataframes de notes
+        qui sera l'index des dataframes de notes
         et donne l'ensemble des inscrits non DEM ni DEF.
         """
         return [inscr.etudid for inscr in self.inscriptions], {
diff --git a/app/scodoc/sco_import_users.py b/app/scodoc/sco_import_users.py
index 8f0ac77008f63f31a6fb0fa538db65a3cb88d3c6..314822a1374c0bbba0787c01cf69b0ba03be8077 100644
--- a/app/scodoc/sco_import_users.py
+++ b/app/scodoc/sco_import_users.py
@@ -140,7 +140,7 @@ def read_users_excel_file(datafile, titles=TITLES) -> list[dict]:
     for line in data[1:]:
         d = {}
         for i, field in enumerate(xls_titles):
-            d[field] = line[i]
+            d[field] = (line[i] or "").strip()
         users.append(d)
     return users
 
diff --git a/app/scodoc/sco_moduleimpl_status.py b/app/scodoc/sco_moduleimpl_status.py
index abe854576bdb666b7641e16524fa366bf1774d80..5897ea46675fa561ddc2d01fafbf3db740fe71e7 100644
--- a/app/scodoc/sco_moduleimpl_status.py
+++ b/app/scodoc/sco_moduleimpl_status.py
@@ -70,13 +70,21 @@ def moduleimpl_evaluation_menu(evaluation: Evaluation, nbnotes: int = 0) -> str:
 
     menu_eval = [
         {
-            "title": "Saisir notes",
+            "title": "Saisir les notes",
             "endpoint": "notes.saisie_notes",
             "args": {
                 "evaluation_id": evaluation_id,
             },
             "enabled": can_edit_notes_ens,
         },
+        {
+            "title": "Saisir par fichier tableur",
+            "id": "menu_saisie_tableur",
+            "endpoint": "notes.saisie_notes_tableur",
+            "args": {
+                "evaluation_id": evaluation.id,
+            },
+        },
         {
             "title": "Modifier évaluation",
             "endpoint": "notes.evaluation_edit",
diff --git a/app/scodoc/sco_saisie_notes.py b/app/scodoc/sco_saisie_notes.py
index d8006e4961d36bd6e6ea95e4c6871ba54f24f572..7df74fa366198e54e614f8d4bcaffbe2f4e1bfbc 100644
--- a/app/scodoc/sco_saisie_notes.py
+++ b/app/scodoc/sco_saisie_notes.py
@@ -29,12 +29,15 @@
 
    Formulaire revu en juillet 2016
 """
+import html
 import time
-import psycopg2
+
 
 import flask
 from flask import g, url_for, request
 from flask_login import current_user
+from flask_sqlalchemy.query import Query
+import psycopg2
 
 from app import db, log
 from app.auth.models import User
@@ -75,8 +78,6 @@ import app.scodoc.sco_utils as scu
 from app.scodoc.sco_utils import json_error
 from app.scodoc.sco_utils import ModuleType
 
-from flask_sqlalchemy.query import Query
-
 
 def convert_note_from_string(
     note: str,
@@ -115,7 +116,7 @@ def convert_note_from_string(
     return note_value, invalid
 
 
-def _displayNote(val):
+def _display_note(val):
     """Convert note from DB to viewable string.
     Utilisé seulement pour I/O vers formulaires (sans perte de precision)
     (Utiliser fmt_note pour les affichages)
@@ -272,7 +273,7 @@ def do_evaluation_upload_xls():
                 diag.append("Notes invalides pour: " + ", ".join(etudsnames))
             raise InvalidNoteValue()
         else:
-            etudids_changed, nb_suppress, etudids_with_decisions = notes_add(
+            etudids_changed, nb_suppress, etudids_with_decisions, messages = notes_add(
                 current_user, evaluation_id, valid_notes, comment
             )
             # news
@@ -292,9 +293,19 @@ def do_evaluation_upload_xls():
                 max_frequency=30 * 60,  # 30 minutes
             )
 
-            msg = f"""<p>{len(etudids_changed)} notes changées ({len(withoutnotes)} sans notes, {
-                        len(absents)} absents, {nb_suppress} note supprimées)
+            msg = f"""<p>
+                {len(etudids_changed)} notes changées ({len(withoutnotes)} sans notes,
+                {len(absents)} absents, {nb_suppress} note supprimées)
                 </p>"""
+            if messages:
+                msg += f"""<div class="warning">Attention&nbsp;:
+                <ul>
+                    <li>{
+                    '</li><li>'.join(messages)
+                    }
+                    </li>
+                </ul>
+                </div>"""
             if etudids_with_decisions:
                 msg += """<p class="warning">Important: il y avait déjà des décisions de jury
                     enregistrées, qui sont peut-être à revoir suite à cette modification !</p>
@@ -322,7 +333,7 @@ def do_evaluation_set_etud_note(evaluation: Evaluation, etud: Identite, value) -
     # Convert and check value
     L, invalids, _, _, _ = _check_notes([(etud.id, value)], evaluation)
     if len(invalids) == 0:
-        etudids_changed, _, _ = notes_add(
+        etudids_changed, _, _, _ = notes_add(
             current_user, evaluation.id, L, "Initialisation notes"
         )
         if len(etudids_changed) == 1:
@@ -398,7 +409,9 @@ def do_evaluation_set_missing(
         )
     # ok
     comment = "Initialisation notes manquantes"
-    etudids_changed, _, _ = notes_add(current_user, evaluation_id, valid_notes, comment)
+    etudids_changed, _, _, _ = notes_add(
+        current_user, evaluation_id, valid_notes, comment
+    )
     # news
     url = url_for(
         "notes.moduleimpl_status",
@@ -456,7 +469,7 @@ def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
     )
 
     if not dialog_confirmed:
-        etudids_changed, nb_suppress, existing_decisions = notes_add(
+        etudids_changed, nb_suppress, existing_decisions, _ = notes_add(
             current_user, evaluation_id, notes, do_it=False, check_inscription=False
         )
         msg = f"""<p>Confirmer la suppression des {nb_suppress} notes ?
@@ -477,7 +490,7 @@ def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
         )
 
     # modif
-    etudids_changed, nb_suppress, existing_decisions = notes_add(
+    etudids_changed, nb_suppress, existing_decisions, _ = notes_add(
         current_user,
         evaluation_id,
         notes,
@@ -519,7 +532,7 @@ def notes_add(
     comment=None,
     do_it=True,
     check_inscription=True,
-) -> tuple[list[int], int, list[int]]:
+) -> tuple[list[int], int, list[int], list[str]]:
     """
     Insert or update notes
     notes is a list of tuples (etudid,value)
@@ -528,30 +541,48 @@ def notes_add(
     Nota:
     - si la note existe deja avec valeur distincte, ajoute une entree au log (notes_notes_log)
 
-    Return: tuple (etudids_changed, nb_suppress, etudids_with_decision)
+    Return: tuple (etudids_changed, nb_suppress, etudids_with_decision, messages)
+
+    messages = list de messages d'avertissement/information pour l'utilisateur
     """
     evaluation = Evaluation.get_evaluation(evaluation_id)
     now = psycopg2.Timestamp(*time.localtime()[:6])
-
-    # Vérifie inscription et valeur note
-    inscrits = {
+    messages = []
+    # Vérifie inscription au module (même DEM/DEF)
+    etudids_inscrits_mod = {
         x[0]
         for x in sco_groups.do_evaluation_listeetuds_groups(
             evaluation_id, getallstudents=True, include_demdef=True
         )
     }
-    # Les étudiants inscrits au semestre ni DEM ni DEF
-    _, etudids_actifs = evaluation.moduleimpl.formsemestre.etudids_actifs()
+    # Les étudiants inscrits au semestre et ceux "actifs" (ni DEM ni DEF)
+    etudids_inscrits_sem, etudids_actifs = (
+        evaluation.moduleimpl.formsemestre.etudids_actifs()
+    )
     for etudid, value in notes:
-        if check_inscription and (
-            (etudid not in inscrits) or (etudid not in etudids_actifs)
-        ):
-            log(f"notes_add: {etudid} non inscrit ou DEM/DEF: aborting")
-            raise NoteProcessError(f"étudiant {etudid} non inscrit dans ce module")
+
+        if check_inscription:
+            msg_err, msg_warn = "", ""
+            if etudid not in etudids_inscrits_sem:
+                msg_err = "non inscrit au semestre"
+            elif etudid not in etudids_inscrits_mod:
+                msg_err = "non inscrit au module"
+            elif etudid not in etudids_actifs:
+                # DEM ou DEF
+                msg_warn = "démissionnaire ou défaillant (note enregistrée)"
+            if msg_err or msg_warn:
+                etud = Identite.query.get(etudid) if isinstance(etudid, int) else None
+                msg = f"étudiant {etud.nomprenom if etud else etudid} {msg_err or msg_warn}"
+            if msg_err:
+                log(f"notes_add: {etudid} non inscrit ou DEM/DEF: aborting")
+                raise NoteProcessError(msg)
+            if msg_warn:
+                messages.append(msg)
         if (value is not None) and not isinstance(value, float):
             log(f"notes_add: {etudid} valeur de note invalide ({value}): aborting")
+            etud = Identite.query.get(etudid) if isinstance(etudid, int) else None
             raise NoteProcessError(
-                f"etudiant {etudid}: valeur de note invalide ({value})"
+                f"etudiant {etud.nomprenom if etud else etudid}: valeur de note invalide ({value})"
             )
     # Recherche notes existantes
     notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
@@ -566,102 +597,20 @@ def notes_add(
     etudids_with_decision = []
     try:
         for etudid, value in notes:
-            changed = False
-            if etudid not in notes_db:
-                # nouvelle note
-                if value != scu.NOTES_SUPPRESS:
-                    if do_it:
-                        args = {
-                            "etudid": etudid,
-                            "evaluation_id": evaluation_id,
-                            "value": value,
-                            "comment": comment,
-                            "uid": user.id,
-                            "date": now,
-                        }
-                        ndb.quote_dict(args)
-                        # Note: le conflit ci-dessous peut arriver si un autre thread
-                        # a modifié la base après qu'on ait lu notes_db
-                        cursor.execute(
-                            """INSERT INTO notes_notes
-                            (etudid, evaluation_id, value, comment, date, uid)
-                            VALUES
-                            (%(etudid)s,%(evaluation_id)s,%(value)s,
-                                %(comment)s,%(date)s,%(uid)s)
-                            ON CONFLICT ON CONSTRAINT notes_notes_etudid_evaluation_id_key
-                            DO UPDATE SET etudid=%(etudid)s, evaluation_id=%(evaluation_id)s,
-                                value=%(value)s, comment=%(comment)s, date=%(date)s, uid=%(uid)s
-                            """,
-                            args,
-                        )
-                    changed = True
-            else:
-                # il y a deja une note
-                oldval = notes_db[etudid]["value"]
-                if type(value) != type(oldval):
-                    changed = True
-                elif isinstance(value, float) and (
-                    abs(value - oldval) > scu.NOTES_PRECISION
-                ):
-                    changed = True
-                elif value != oldval:
-                    changed = True
-                if changed:
-                    # recopie l'ancienne note dans notes_notes_log, puis update
-                    if do_it:
-                        cursor.execute(
-                            """INSERT INTO notes_notes_log
-                                (etudid,evaluation_id,value,comment,date,uid)
-                            SELECT etudid, evaluation_id, value, comment, date, uid
-                            FROM notes_notes
-                            WHERE etudid=%(etudid)s
-                            and evaluation_id=%(evaluation_id)s
-                            """,
-                            {"etudid": etudid, "evaluation_id": evaluation_id},
-                        )
-                        args = {
-                            "etudid": etudid,
-                            "evaluation_id": evaluation_id,
-                            "value": value,
-                            "date": now,
-                            "comment": comment,
-                            "uid": user.id,
-                        }
-                        ndb.quote_dict(args)
-                    if value != scu.NOTES_SUPPRESS:
-                        if do_it:
-                            cursor.execute(
-                                """UPDATE notes_notes
-                                SET value=%(value)s, comment=%(comment)s, date=%(date)s, uid=%(uid)s
-                                WHERE etudid = %(etudid)s
-                                and evaluation_id = %(evaluation_id)s
-                                """,
-                                args,
-                            )
-                    else:  # suppression ancienne note
-                        if do_it:
-                            log(
-                                f"""notes_add, suppress, evaluation_id={evaluation_id}, etudid={
-                                    etudid}, oldval={oldval}"""
-                            )
-                            cursor.execute(
-                                """DELETE FROM notes_notes
-                                WHERE etudid = %(etudid)s
-                                AND evaluation_id = %(evaluation_id)s
-                                """,
-                                args,
-                            )
-                            # garde trace de la suppression dans l'historique:
-                            args["value"] = scu.NOTES_SUPPRESS
-                            cursor.execute(
-                                """INSERT INTO notes_notes_log
-                                    (etudid,evaluation_id,value,comment,date,uid)
-                                VALUES
-                                (%(etudid)s, %(evaluation_id)s, %(value)s, %(comment)s, %(date)s, %(uid)s)
-                                """,
-                                args,
-                            )
-                        nb_suppress += 1
+            changed, suppressed = _record_note(
+                cursor,
+                notes_db,
+                etudid,
+                evaluation_id,
+                value,
+                comment=comment,
+                user=user,
+                date=now,
+                do_it=do_it,
+            )
+            if suppressed:
+                nb_suppress += 1
+
             if changed:
                 etudids_changed.append(etudid)
                 if res.etud_has_decision(etudid, include_rcues=False):
@@ -678,7 +627,108 @@ def notes_add(
         cnx.commit()
         sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre.id)
         sco_cache.EvaluationCache.delete(evaluation_id)
-    return etudids_changed, nb_suppress, etudids_with_decision
+    return etudids_changed, nb_suppress, etudids_with_decision, messages
+
+
+def _record_note(
+    cursor,
+    notes_db,
+    etudid: int,
+    evaluation_id: int,
+    value: float,
+    comment: str = "",
+    user: User = None,
+    date=None,
+    do_it=False,
+):
+    "Enregistrement de la note en base"
+    changed = False
+    suppressed = False
+    args = {
+        "etudid": etudid,
+        "evaluation_id": evaluation_id,
+        "value": value,
+        # convention scodoc7 quote comment:
+        "comment": (html.escape(comment) if isinstance(comment, str) else comment),
+        "uid": user.id,
+        "date": date,
+    }
+    if etudid not in notes_db:
+        # nouvelle note
+        if value != scu.NOTES_SUPPRESS:
+            if do_it:
+                # Note: le conflit ci-dessous peut arriver si un autre thread
+                # a modifié la base après qu'on ait lu notes_db
+                cursor.execute(
+                    """INSERT INTO notes_notes
+                    (etudid, evaluation_id, value, comment, date, uid)
+                    VALUES
+                    (%(etudid)s,%(evaluation_id)s,%(value)s,
+                        %(comment)s,%(date)s,%(uid)s)
+                    ON CONFLICT ON CONSTRAINT notes_notes_etudid_evaluation_id_key
+                    DO UPDATE SET etudid=%(etudid)s, evaluation_id=%(evaluation_id)s,
+                        value=%(value)s, comment=%(comment)s, date=%(date)s, uid=%(uid)s
+                    """,
+                    args,
+                )
+            changed = True
+    else:
+        # il y a deja une note
+        oldval = notes_db[etudid]["value"]
+        if type(value) != type(oldval):
+            changed = True
+        elif isinstance(value, float) and (abs(value - oldval) > scu.NOTES_PRECISION):
+            changed = True
+        elif value != oldval:
+            changed = True
+        if changed:
+            # recopie l'ancienne note dans notes_notes_log, puis update
+            if do_it:
+                cursor.execute(
+                    """INSERT INTO notes_notes_log
+                        (etudid,evaluation_id,value,comment,date,uid)
+                    SELECT etudid, evaluation_id, value, comment, date, uid
+                    FROM notes_notes
+                    WHERE etudid=%(etudid)s
+                    and evaluation_id=%(evaluation_id)s
+                    """,
+                    args,
+                )
+            if value != scu.NOTES_SUPPRESS:
+                if do_it:
+                    cursor.execute(
+                        """UPDATE notes_notes
+                        SET value=%(value)s, comment=%(comment)s, date=%(date)s, uid=%(uid)s
+                        WHERE etudid = %(etudid)s
+                        and evaluation_id = %(evaluation_id)s
+                        """,
+                        args,
+                    )
+            else:  # suppression ancienne note
+                if do_it:
+                    log(
+                        f"""notes_add, suppress, evaluation_id={evaluation_id}, etudid={
+                            etudid}, oldval={oldval}"""
+                    )
+                    cursor.execute(
+                        """DELETE FROM notes_notes
+                        WHERE etudid = %(etudid)s
+                        AND evaluation_id = %(evaluation_id)s
+                        """,
+                        args,
+                    )
+                    # garde trace de la suppression dans l'historique:
+                    args["value"] = scu.NOTES_SUPPRESS
+                    cursor.execute(
+                        """INSERT INTO notes_notes_log
+                            (etudid,evaluation_id,value,comment,date,uid)
+                        VALUES
+                        (%(etudid)s, %(evaluation_id)s, %(value)s, %(comment)s, %(date)s, %(uid)s)
+                        """,
+                        args,
+                    )
+                suppressed = True
+    return changed, suppressed
 
 
 def saisie_notes_tableur(evaluation_id, group_ids=()):
@@ -703,7 +753,7 @@ def saisie_notes_tableur(evaluation_id, group_ids=()):
         )
 
     page_title = "Saisie des notes" + (
-        f"""de {evaluation.description}""" if evaluation.description else ""
+        f""" de {evaluation.description}""" if evaluation.description else ""
     )
 
     # Informations sur les groupes à afficher:
@@ -797,9 +847,13 @@ def saisie_notes_tableur(evaluation_id, group_ids=()):
             }">
             Revenir au tableau de bord du module</a>
             &nbsp;&nbsp;&nbsp;
+            <a class="stdlink" href="{url_for("notes.saisie_notes_tableur",
+                scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
+            }">Charger un autre fichier de notes</a>
+            &nbsp;&nbsp;&nbsp;
             <a class="stdlink" href="{url_for("notes.saisie_notes",
                 scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
-            }">Charger d'autres notes dans cette évaluation</a>
+            }">Formulaire de saisie des notes</a>
             </p>"""
             )
         else:
@@ -1015,7 +1069,7 @@ def saisie_notes(evaluation_id: int, group_ids: list = None):
             "Autres opérations",
             [
                 {
-                    "title": "Saisie par fichier tableur",
+                    "title": "Saisir par fichier tableur",
                     "id": "menu_saisie_tableur",
                     "endpoint": "notes.saisie_notes_tableur",
                     "args": {
@@ -1126,7 +1180,7 @@ def _get_sorted_etuds(evaluation: Evaluation, etudids: list, formsemestre_id: in
 
         # Note actuelle de l'étudiant:
         if etudid in notes_db:
-            e["val"] = _displayNote(notes_db[etudid]["value"])
+            e["val"] = _display_note(notes_db[etudid]["value"])
             comment = notes_db[etudid]["comment"]
             if comment is None:
                 comment = ""
@@ -1368,7 +1422,7 @@ def save_notes(
     #
     valid_notes, _, _, _, _ = _check_notes(notes, evaluation)
     if valid_notes:
-        etudids_changed, _, etudids_with_decision = notes_add(
+        etudids_changed, _, etudids_with_decision, messages = notes_add(
             current_user, evaluation.id, valid_notes, comment=comment, do_it=True
         )
         ScolarNews.add(
@@ -1386,12 +1440,14 @@ def save_notes(
                 etudid: get_note_history_menu(evaluation.id, etudid)
                 for etudid in etudids_changed
             },
+            "messages": messages,
         }
     else:
         result = {
             "etudids_changed": [],
             "etudids_with_decision": [],
             "history_menu": [],
+            "messages": [],
         }
 
     return result
@@ -1420,7 +1476,7 @@ def get_note_history_menu(evaluation_id: int, etudid: int) -> str:
     first = True
     for i in history:
         jt = i["date"].strftime("le %d/%m/%Y à %H:%M") + " (%s)" % i["user_name"]
-        dispnote = _displayNote(i["value"])
+        dispnote = _display_note(i["value"])
         if first:
             nv = ""  # ne repete pas la valeur de la note courante
         else:
diff --git a/app/scodoc/sco_ue_external.py b/app/scodoc/sco_ue_external.py
index f1840386bf12cb29872b26860899266a143498a2..ff5bca9bc25b71e62fedaefc2b5de4f35f4543ba 100644
--- a/app/scodoc/sco_ue_external.py
+++ b/app/scodoc/sco_ue_external.py
@@ -180,7 +180,7 @@ def external_ue_inscrit_et_note(
             description="note externe",
         )
     # Saisie des notes
-    _, _, _ = sco_saisie_notes.notes_add(
+    _, _, _, _ = sco_saisie_notes.notes_add(
         current_user,
         evaluation.id,
         list(notes_etuds.items()),
diff --git a/app/scodoc/sco_users.py b/app/scodoc/sco_users.py
index eb19901b30cebf423c59eb3a626faa3a0329a51f..f1ab31917c4f9b8bdfd8eb8705972e2c4bf7614b 100644
--- a/app/scodoc/sco_users.py
+++ b/app/scodoc/sco_users.py
@@ -35,7 +35,7 @@ from flask import url_for, g, request
 from flask_login import current_user
 
 
-from app import db, Departement
+from app import Departement
 
 from app.auth.models import Permission, Role, User, UserRole
 from app.models import ScoDocSiteConfig, USERNAME_STR_LEN
diff --git a/sco_version.py b/sco_version.py
index 0a1eeda1f319292ff557bf1c5578363c597145c2..2d37b22ba4b97ce1660b219273cfc1191a46a800 100644
--- a/sco_version.py
+++ b/sco_version.py
@@ -1,7 +1,7 @@
 # -*- mode: python -*-
 # -*- coding: utf-8 -*-
 
-SCOVERSION = "9.6.975"
+SCOVERSION = "9.6.976"
 
 SCONAME = "ScoDoc"
 
diff --git a/tests/unit/test_notes_modules.py b/tests/unit/test_notes_modules.py
index 1542eaecda24023b7acbae1ff4fab49c9ce0ad9a..7a5a9ea565e406248eae0980172056837a551be7 100644
--- a/tests/unit/test_notes_modules.py
+++ b/tests/unit/test_notes_modules.py
@@ -2,6 +2,7 @@
     Vérif moyennes de modules des bulletins
     et aussi moyennes modules et UE internes (via nt)
 """
+
 import datetime
 import numpy as np
 from flask import g
@@ -107,16 +108,16 @@ def test_notes_modules(test_client):
     # --- Notes ordinaires
     note_1 = 12.0
     note_2 = 13.0
-    _, _, _ = G.create_note(
+    _ = G.create_note(
         evaluation_id=e1["evaluation_id"], etudid=etuds[0]["etudid"], note=note_1
     )
-    _, _, _ = G.create_note(
+    _ = G.create_note(
         evaluation_id=e2["evaluation_id"], etudid=etuds[0]["etudid"], note=note_2
     )
-    _, _, _ = G.create_note(
+    _ = G.create_note(
         evaluation_id=e1["evaluation_id"], etudid=etuds[1]["etudid"], note=note_1 / 2
     )
-    _, _, _ = G.create_note(
+    _ = G.create_note(
         evaluation_id=e2["evaluation_id"], etudid=etuds[1]["etudid"], note=note_2 / 3
     )
     b = sco_bulletins.formsemestre_bulletinetud_dict(
@@ -139,22 +140,20 @@ def test_notes_modules(test_client):
     )
 
     # Absence à une évaluation
-    _, _, _ = G.create_note(
+    _ = G.create_note(
         evaluation_id=e1["evaluation_id"], etudid=etudid, note=None
     )  # abs
-    _, _, _ = G.create_note(
-        evaluation_id=e2["evaluation_id"], etudid=etudid, note=note_2
-    )
+    _ = G.create_note(evaluation_id=e2["evaluation_id"], etudid=etudid, note=note_2)
     b = sco_bulletins.formsemestre_bulletinetud_dict(
         sem["formsemestre_id"], etud["etudid"]
     )
     note_th = (coef_1 * 0.0 + coef_2 * note_2) / (coef_1 + coef_2)
     assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(note_th)
     # Absences aux deux évaluations
-    _, _, _ = G.create_note(
+    _ = G.create_note(
         evaluation_id=e1["evaluation_id"], etudid=etudid, note=None
     )  # abs
-    _, _, _ = G.create_note(
+    _ = G.create_note(
         evaluation_id=e2["evaluation_id"], etudid=etudid, note=None
     )  # abs
     b = sco_bulletins.formsemestre_bulletinetud_dict(
@@ -171,10 +170,8 @@ def test_notes_modules(test_client):
     )
 
     # Note excusée EXC <-> scu.NOTES_NEUTRALISE
-    _, _, _ = G.create_note(
-        evaluation_id=e1["evaluation_id"], etudid=etudid, note=note_1
-    )
-    _, _, _ = G.create_note(
+    _ = G.create_note(evaluation_id=e1["evaluation_id"], etudid=etudid, note=note_1)
+    _ = G.create_note(
         evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
     )  # EXC
     b = sco_bulletins.formsemestre_bulletinetud_dict(
@@ -190,10 +187,8 @@ def test_notes_modules(test_client):
         expected_moy_ue=note_1,
     )
     # Note en attente ATT <-> scu.NOTES_ATTENTE
-    _, _, _ = G.create_note(
-        evaluation_id=e1["evaluation_id"], etudid=etudid, note=note_1
-    )
-    _, _, _ = G.create_note(
+    _ = G.create_note(evaluation_id=e1["evaluation_id"], etudid=etudid, note=note_1)
+    _ = G.create_note(
         evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_ATTENTE
     )  # ATT
     b = sco_bulletins.formsemestre_bulletinetud_dict(
@@ -209,10 +204,10 @@ def test_notes_modules(test_client):
         expected_moy_ue=note_1,
     )
     # Neutralisation (EXC) des 2 évals
-    _, _, _ = G.create_note(
+    _ = G.create_note(
         evaluation_id=e1["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
     )  # EXC
-    _, _, _ = G.create_note(
+    _ = G.create_note(
         evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
     )  # EXC
     b = sco_bulletins.formsemestre_bulletinetud_dict(
@@ -228,10 +223,10 @@ def test_notes_modules(test_client):
         expected_moy_ue=np.nan,
     )
     # Attente (ATT) sur les 2 evals
-    _, _, _ = G.create_note(
+    _ = G.create_note(
         evaluation_id=e1["evaluation_id"], etudid=etudid, note=scu.NOTES_ATTENTE
     )  # ATT
-    _, _, _ = G.create_note(
+    _ = G.create_note(
         evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_ATTENTE
     )  # ATT
     b = sco_bulletins.formsemestre_bulletinetud_dict(
@@ -290,7 +285,7 @@ def test_notes_modules(test_client):
         {"etudid": etudid, "moduleimpl_id": moduleimpl_id},
         formsemestre_id=formsemestre_id,
     )
-    _, _, _ = G.create_note(evaluation_id=e1["evaluation_id"], etudid=etudid, note=12.5)
+    _ = G.create_note(evaluation_id=e1["evaluation_id"], etudid=etudid, note=12.5)
 
     nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
     mod_stats = nt.get_mod_stats(moduleimpl_id)
@@ -318,9 +313,7 @@ def test_notes_modules(test_client):
         description="evaluation mod 2",
         coefficient=1.0,
     )
-    _, _, _ = G.create_note(
-        evaluation_id=e_m2["evaluation_id"], etudid=etudid, note=19.5
-    )
+    _ = G.create_note(evaluation_id=e_m2["evaluation_id"], etudid=etudid, note=19.5)
     formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
     nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
     ue_status = nt.get_etud_ue_status(etudid, ue_id)
@@ -328,22 +321,20 @@ def test_notes_modules(test_client):
     # Moyenne d'UE si l'un des modules est EXC ("NA")
     # 2 modules, notes EXC dans le premier, note valide n dans le second
     # la moyenne de l'UE doit être n
-    _, _, _ = G.create_note(
+    _ = G.create_note(
         evaluation_id=e1["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
     )  # EXC
-    _, _, _ = G.create_note(
+    _ = G.create_note(
         evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
     )  # EXC
-    _, _, _ = G.create_note(
-        evaluation_id=e_m2["evaluation_id"], etudid=etudid, note=12.5
-    )
-    _, _, _ = G.create_note(
+    _ = G.create_note(evaluation_id=e_m2["evaluation_id"], etudid=etudid, note=12.5)
+    _ = G.create_note(
         evaluation_id=e1["evaluation_id"], etudid=etuds[1]["etudid"], note=11.0
     )
-    _, _, _ = G.create_note(
+    _ = G.create_note(
         evaluation_id=e2["evaluation_id"], etudid=etuds[1]["etudid"], note=11.0
     )
-    _, _, _ = G.create_note(
+    _ = G.create_note(
         evaluation_id=e_m2["evaluation_id"], etudid=etuds[1]["etudid"], note=11.0
     )
 
@@ -407,12 +398,12 @@ def test_notes_modules_att_dem(test_client):
         coefficient=coef_1,
     )
     # Attente (ATT) sur les 2 evals
-    _, _, _ = G.create_note(
+    _ = G.create_note(
         evaluation_id=e1["evaluation_id"],
         etudid=etuds[0]["etudid"],
         note=scu.NOTES_ATTENTE,
     )  # ATT
-    _, _, _ = G.create_note(
+    _ = G.create_note(
         evaluation_id=e1["evaluation_id"],
         etudid=etuds[1]["etudid"],
         note=scu.NOTES_ATTENTE,
@@ -455,7 +446,7 @@ def test_notes_modules_att_dem(test_client):
     assert note_e1 == scu.NOTES_ATTENTE  # XXXX un peu contestable
 
     # Saisie note ABS pour le deuxième etud
-    _, _, _ = G.create_note(
+    _ = G.create_note(
         evaluation_id=e1["evaluation_id"], etudid=etuds[1]["etudid"], note=None
     )
     nt = check_nt(
diff --git a/tests/unit/test_notes_rattrapage.py b/tests/unit/test_notes_rattrapage.py
index 22cd1e4755afaeb72d39993f525d6ac3ffdaae0b..4ee29c328b1a423fc2ff01fe0a41e3e9558bde5d 100644
--- a/tests/unit/test_notes_rattrapage.py
+++ b/tests/unit/test_notes_rattrapage.py
@@ -72,8 +72,8 @@ def test_notes_rattrapage(test_client):
         evaluation_type=Evaluation.EVALUATION_RATTRAPAGE,
     )
     etud = etuds[0]
-    _, _, _ = G.create_note(evaluation_id=e["id"], etudid=etud["etudid"], note=12.0)
-    _, _, _ = G.create_note(evaluation_id=e_rat["id"], etudid=etud["etudid"], note=11.0)
+    _ = G.create_note(evaluation_id=e["id"], etudid=etud["etudid"], note=12.0)
+    _ = G.create_note(evaluation_id=e_rat["id"], etudid=etud["etudid"], note=11.0)
 
     # --- Vérifications internes structures ScoDoc
     formsemestre = db.session.get(FormSemestre, formsemestre_id)
@@ -98,23 +98,21 @@ def test_notes_rattrapage(test_client):
     # Note moyenne: ici le ratrapage est inférieur à la note:
     assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(12.0)
     # rattrapage > moyenne:
-    _, _, _ = G.create_note(evaluation_id=e_rat["id"], etudid=etud["etudid"], note=18.0)
+    _ = G.create_note(evaluation_id=e_rat["id"], etudid=etud["etudid"], note=18.0)
     b = sco_bulletins.formsemestre_bulletinetud_dict(
         sem["formsemestre_id"], etud["etudid"]
     )
     assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(18.0)
     # rattrapage vs absences
-    _, _, _ = G.create_note(
-        evaluation_id=e["id"], etudid=etud["etudid"], note=None
-    )  # abs
-    _, _, _ = G.create_note(evaluation_id=e_rat["id"], etudid=etud["etudid"], note=17.0)
+    _ = G.create_note(evaluation_id=e["id"], etudid=etud["etudid"], note=None)  # abs
+    _ = G.create_note(evaluation_id=e_rat["id"], etudid=etud["etudid"], note=17.0)
     b = sco_bulletins.formsemestre_bulletinetud_dict(
         sem["formsemestre_id"], etud["etudid"]
     )
     assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(17.0)
     # et sans note de rattrapage
-    _, _, _ = G.create_note(evaluation_id=e["id"], etudid=etud["etudid"], note=10.0)
-    _, _, _ = G.create_note(evaluation_id=e_rat["id"], etudid=etud["etudid"], note=None)
+    _ = G.create_note(evaluation_id=e["id"], etudid=etud["etudid"], note=10.0)
+    _ = G.create_note(evaluation_id=e_rat["id"], etudid=etud["etudid"], note=None)
     b = sco_bulletins.formsemestre_bulletinetud_dict(
         sem["formsemestre_id"], etud["etudid"]
     )
@@ -159,18 +157,14 @@ def test_notes_rattrapage(test_client):
     assert len(mod_res.get_evaluations_completes(moduleimpl)) == 2
 
     # Saisie note session 2:
-    _, _, _ = G.create_note(
-        evaluation_id=e_session2["id"], etudid=etud["etudid"], note=5.0
-    )
+    _ = G.create_note(evaluation_id=e_session2["id"], etudid=etud["etudid"], note=5.0)
     b = sco_bulletins.formsemestre_bulletinetud_dict(
         sem["formsemestre_id"], etud["etudid"]
     )
     # Note moyenne: utilise session 2 même si inférieure
     assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(5.0)
 
-    _, _, _ = G.create_note(
-        evaluation_id=e_session2["id"], etudid=etud["etudid"], note=20.0
-    )
+    _ = G.create_note(evaluation_id=e_session2["id"], etudid=etud["etudid"], note=20.0)
     b = sco_bulletins.formsemestre_bulletinetud_dict(
         sem["formsemestre_id"], etud["etudid"]
     )
@@ -178,16 +172,14 @@ def test_notes_rattrapage(test_client):
     assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(20.0)
 
     # Met la note session2 à ABS (None)
-    _, _, _ = G.create_note(
-        evaluation_id=e_session2["id"], etudid=etud["etudid"], note=None
-    )
+    _ = G.create_note(evaluation_id=e_session2["id"], etudid=etud["etudid"], note=None)
     b = sco_bulletins.formsemestre_bulletinetud_dict(
         sem["formsemestre_id"], etud["etudid"]
     )
     # Note moyenne: zéro car ABS
     assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(0.0)
     # Supprime note session 2
-    _, _, _ = G.create_note(
+    _ = G.create_note(
         evaluation_id=e_session2["id"], etudid=etud["etudid"], note=scu.NOTES_SUPPRESS
     )
     b = sco_bulletins.formsemestre_bulletinetud_dict(
@@ -216,18 +208,14 @@ def test_notes_rattrapage(test_client):
     # Note moyenne sans bonus
     assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(10.0)
     # Saisie note bonus
-    _, _, _ = G.create_note(
-        evaluation_id=e_bonus["id"], etudid=etud["etudid"], note=1.0
-    )
+    _ = G.create_note(evaluation_id=e_bonus["id"], etudid=etud["etudid"], note=1.0)
     b = sco_bulletins.formsemestre_bulletinetud_dict(
         sem["formsemestre_id"], etud["etudid"]
     )
     # Note moyenne sans bonus
     assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(11.0)
     # Négatif, avec clip à zéro
-    _, _, _ = G.create_note(
-        evaluation_id=e_bonus["id"], etudid=etud["etudid"], note=-20.0
-    )
+    _ = G.create_note(evaluation_id=e_bonus["id"], etudid=etud["etudid"], note=-20.0)
     b = sco_bulletins.formsemestre_bulletinetud_dict(
         sem["formsemestre_id"], etud["etudid"]
     )
diff --git a/tests/unit/test_sco_basic.py b/tests/unit/test_sco_basic.py
index b3bba2e201c29e75e9755ddf3cdaa5524eca9084..e159391c46295cc3308593741926a0b3d13d899a 100644
--- a/tests/unit/test_sco_basic.py
+++ b/tests/unit/test_sco_basic.py
@@ -105,7 +105,7 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre:
 
     # --- Saisie toutes les notes de l'évaluation
     for idx, etud in enumerate(etuds):
-        etudids_changed, nb_suppress, existing_decisions = G.create_note(
+        etudids_changed, nb_suppress, existing_decisions, messages = G.create_note(
             evaluation_id=e1.id,
             etudid=etud["etudid"],
             note=NOTES_T[idx % len(NOTES_T)],
@@ -113,6 +113,7 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre:
         assert not existing_decisions
         assert nb_suppress == 0
         assert len(etudids_changed) == 1
+        assert messages == []
 
     # --- Vérifie que les notes sont prises en compte:
     b = sco_bulletins.formsemestre_bulletinetud_dict(formsemestre_id, etud["etudid"])
@@ -139,11 +140,12 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre:
     db.session.commit()
     # Saisie les notes des 5 premiers étudiants:
     for idx, etud in enumerate(etuds[:5]):
-        etudids_changed, nb_suppress, existing_decisions = G.create_note(
+        etudids_changed, nb_suppress, existing_decisions, messages = G.create_note(
             evaluation_id=e2.id,
             etudid=etud["etudid"],
             note=NOTES_T[idx % len(NOTES_T)],
         )
+        assert messages == []
     # Cette éval n'est pas complète
     etat = sco_evaluations.do_evaluation_etat(e2.id)
     assert etat["evalcomplete"] is False
@@ -162,11 +164,12 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre:
 
     # Saisie des notes qui manquent:
     for idx, etud in enumerate(etuds[5:]):
-        etudids_changed, nb_suppress, existing_decisions = G.create_note(
+        etudids_changed, nb_suppress, existing_decisions, messages = G.create_note(
             evaluation_id=e2.id,
             etudid=etud["etudid"],
             note=NOTES_T[idx % len(NOTES_T)],
         )
+        assert messages == []
     etat = sco_evaluations.do_evaluation_etat(e2.id)
     assert etat["evalcomplete"]
     assert etat["nb_att"] == 0