diff --git a/app/but/bulletin_but.py b/app/but/bulletin_but.py
index fc680dbc717c3f72a698c1c3660e49291b098a35..4d00821459b302381d4e39c85023e6eb9c1b3429 100644
--- a/app/but/bulletin_but.py
+++ b/app/but/bulletin_but.py
@@ -386,6 +386,12 @@ class BulletinBUT:
             semestre_infos["absences"] = {
                 "injustifie": nbabs - nbabsjust,
                 "total": nbabs,
+                "metrique": {
+                    "H.": "Heure(s)",
+                    "J.": "Journée(s)",
+                    "1/2 J.": "1/2 Jour.",
+                    "N.": "Nombre",
+                }.get(sco_preferences.get_preference("assi_metrique")),
             }
         decisions_ues = self.res.get_etud_decisions_ue(etud.id) or {}
         if self.prefs["bul_show_ects"]:
diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py
index 68399262b628892a31c4b015f826cb794b19c888..eef4cdd88597621d44b8ed804373b670065cc8a0 100644
--- a/app/models/formsemestre.py
+++ b/app/models/formsemestre.py
@@ -39,9 +39,11 @@ from app.models.validations import ScolarFormSemestreValidation
 from app.scodoc import codes_cursus, sco_preferences
 from app.scodoc.sco_exceptions import ScoValueError
 from app.scodoc.sco_permissions import Permission
-from app.scodoc.sco_utils import MONTH_NAMES_ABBREV
+from app.scodoc.sco_utils import MONTH_NAMES_ABBREV, translate_assiduites_metric
 from app.scodoc.sco_vdi import ApoEtapeVDI
 
+from app.scodoc.sco_utils import translate_assiduites_metric
+
 GROUPS_AUTO_ASSIGNMENT_DATA_MAX = 1024 * 1024  # bytes
 
 
@@ -678,8 +680,12 @@ class FormSemestre(db.Model):
         """
         from app.scodoc import sco_abs
 
-        return sco_abs.get_abs_count_in_interval(
-            etudid, self.date_debut.isoformat(), self.date_fin.isoformat()
+        metrique = sco_preferences.get_preference("assi_metrique", self.id)
+        return sco_abs.get_assiduites_count_in_interval(
+            etudid,
+            self.date_debut.isoformat(),
+            self.date_fin.isoformat(),
+            translate_assiduites_metric(metrique),
         )
 
     def get_codes_apogee(self, category=None) -> set[str]:
diff --git a/app/scodoc/html_sidebar.py b/app/scodoc/html_sidebar.py
index b0d4a234839f84188da59c3bd75eb4a51d96153d..6d09becc294ce66f7357770c3955f05652aa0510 100755
--- a/app/scodoc/html_sidebar.py
+++ b/app/scodoc/html_sidebar.py
@@ -116,10 +116,10 @@ def sidebar(etudid: int = None):
         )
         if etud["cursem"]:
             cur_sem = etud["cursem"]
-            nbabs, nbabsjust = sco_abs.get_abs_count(etudid, cur_sem)
+            nbabs, nbabsjust = sco_abs.get_assiduites_count(etudid, cur_sem)
             nbabsnj = nbabs - nbabsjust
             H.append(
-                f"""<span title="absences du { cur_sem["date_debut"] } au { cur_sem["date_fin"] }">(1/2 j.)
+                f"""<span title="absences du { cur_sem["date_debut"] } au { cur_sem["date_fin"] }">({sco_preferences.get_preference("assi_metrique", None)})
                 <br>{ nbabsjust } J., { nbabsnj } N.J.</span>"""
             )
         H.append("<ul>")
diff --git a/app/scodoc/sco_abs.py b/app/scodoc/sco_abs.py
index e1672492b1893812d772d8cd5dcfbec13bbeb26c..f34ab6d33203b063d6da890ccbff550a443df975 100755
--- a/app/scodoc/sco_abs.py
+++ b/app/scodoc/sco_abs.py
@@ -1054,19 +1054,42 @@ def get_abs_count_in_interval(etudid, date_debut_iso, date_fin_iso):
     return r
 
 
-def get_assiduites_count_in_interval(etudid, date_debut_iso, date_fin_iso):
+def get_assiduites_count(etudid, sem):
+    """Les comptes d'absences de cet étudiant dans ce semestre:
+    tuple (nb abs non justifiées, nb abs justifiées)
+    Utilise un cache.
+    """
+    metrique = sco_preferences.get_preference("assi_metrique", sem["formsemestre_id"])
+    return get_assiduites_count_in_interval(
+        etudid,
+        sem["date_debut_iso"],
+        sem["date_fin_iso"],
+        scu.translate_assiduites_metric(metrique),
+    )
+
+
+def get_assiduites_count_in_interval(
+    etudid, date_debut_iso, date_fin_iso, metrique="demi"
+):
     """Les comptes d'absences de cet étudiant entre ces deux dates, incluses:
     tuple (nb abs, nb abs justifiées)
     Utilise un cache.
     """
-    key = str(etudid) + "_" + date_debut_iso + "_" + date_fin_iso + "_assiduites"
+    key = (
+        str(etudid)
+        + "_"
+        + date_debut_iso
+        + "_"
+        + date_fin_iso
+        + f"{metrique}_assiduites"
+    )
     r = sco_cache.AbsSemEtudCache.get(key)
     if not r:
-
         date_debut: datetime.datetime = scu.is_iso_formated(date_debut_iso, True)
         date_fin: datetime.datetime = scu.is_iso_formated(date_fin_iso, True)
 
         assiduites: Assiduite = Assiduite.query.filter_by(etudid=etudid)
+        assiduites = assiduites.filter(Assiduite.etat != 0)
         justificatifs: Justificatif = Justificatif.query.filter_by(etudid=etudid)
 
         assiduites = scass.filter_by_date(assiduites, Assiduite, date_debut, date_fin)
@@ -1076,7 +1099,7 @@ def get_assiduites_count_in_interval(etudid, date_debut_iso, date_fin_iso):
 
         calculator: scass.CountCalculator = scass.CountCalculator()
         calculator.compute_assiduites(assiduites)
-        nb_abs: dict = calculator.to_dict()["demi"]
+        nb_abs: dict = calculator.to_dict()[metrique]
 
         abs_just: list[Assiduite] = scass.get_all_justified(
             etudid, date_debut, date_fin
@@ -1084,7 +1107,7 @@ def get_assiduites_count_in_interval(etudid, date_debut_iso, date_fin_iso):
 
         calculator.reset()
         calculator.compute_assiduites(abs_just)
-        nb_abs_just: dict = calculator.to_dict()["demi"]
+        nb_abs_just: dict = calculator.to_dict()[metrique]
 
         r = (nb_abs, nb_abs_just)
         ans = sco_cache.AbsSemEtudCache.set(key, r)
@@ -1101,6 +1124,15 @@ def invalidate_abs_count(etudid, sem):
     sco_cache.AbsSemEtudCache.delete(key)
 
 
+def invalidate_assiduites_count(etudid, sem):
+    """Invalidate (clear) cached counts"""
+    date_debut = sem["date_debut_iso"]
+    date_fin = sem["date_fin_iso"]
+    for met in ["demi", "journee", "compte", "heure"]:
+        key = str(etudid) + "_" + date_debut + "_" + date_fin + f"{met}_assiduites"
+        sco_cache.AbsSemEtudCache.delete(key)
+
+
 def invalidate_abs_count_sem(sem):
     """Invalidate (clear) cached abs counts for all the students of this semestre"""
     inscriptions = (
@@ -1112,6 +1144,17 @@ def invalidate_abs_count_sem(sem):
         invalidate_abs_count(ins["etudid"], sem)
 
 
+def invalidate_assiduites_count_sem(sem):
+    """Invalidate (clear) cached abs counts for all the students of this semestre"""
+    inscriptions = (
+        sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits(
+            sem["formsemestre_id"]
+        )
+    )
+    for ins in inscriptions:
+        invalidate_assiduites_count(ins["etudid"], sem)
+
+
 def invalidate_abs_etud_date(etudid, date):  # was invalidateAbsEtudDate
     """Doit etre appelé à chaque modification des absences pour cet étudiant et cette date.
     Invalide cache absence et caches semestre
@@ -1145,3 +1188,38 @@ def invalidate_abs_etud_date(etudid, date):  # was invalidateAbsEtudDate
 
         # Inval cache compteurs absences:
         invalidate_abs_count_sem(sem)
+
+
+def invalidate_assiduites_etud_date(etudid, date):
+    """Doit etre appelé à chaque modification des assiduites pour cet étudiant et cette date.
+    Invalide cache absence et caches semestre
+    date: date au format ISO
+    """
+    from app.scodoc import sco_compute_moy
+
+    # Semestres a cette date:
+    etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
+    sems = [
+        sem
+        for sem in etud["sems"]
+        if sem["date_debut_iso"] <= date and sem["date_fin_iso"] >= date
+    ]
+
+    # Invalide les PDF et les absences:
+    for sem in sems:
+        # Inval cache bulletin et/ou note_table
+        if sco_compute_moy.formsemestre_expressions_use_abscounts(
+            sem["formsemestre_id"]
+        ):
+            # certaines formules utilisent les absences
+            pdfonly = False
+        else:
+            # efface toujours le PDF car il affiche en général les absences
+            pdfonly = True
+
+        sco_cache.invalidate_formsemestre(
+            formsemestre_id=sem["formsemestre_id"], pdfonly=pdfonly
+        )
+
+        # Inval cache compteurs absences:
+        invalidate_assiduites_count(etudid, sem)
diff --git a/app/scodoc/sco_abs_notification.py b/app/scodoc/sco_abs_notification.py
index c01aa4915d0f0c25a841d17c0bd4b60bd0f5e832..88d1d785e8928f05e42c119a8b33846b7d5eb3d7 100644
--- a/app/scodoc/sco_abs_notification.py
+++ b/app/scodoc/sco_abs_notification.py
@@ -47,6 +47,7 @@ import app.scodoc.notesdb as ndb
 from app.scodoc import sco_etud
 from app.scodoc import sco_preferences
 from app.scodoc import sco_users
+from app.scodoc import sco_utils as scu
 
 
 def abs_notify(etudid, date):
@@ -61,8 +62,15 @@ def abs_notify(etudid, date):
     if not formsemestre:
         return  # non inscrit a la date, pas de notification
 
-    nbabs, nbabsjust = sco_abs.get_abs_count_in_interval(
-        etudid, formsemestre.date_debut.isoformat(), formsemestre.date_fin.isoformat()
+    nbabs, nbabsjust = sco_abs.get_assiduites_count_in_interval(
+        etudid,
+        formsemestre.date_debut.isoformat(),
+        formsemestre.date_fin.isoformat(),
+        scu.translate_assiduites_metric(
+            sco_preferences.get_preference(
+                "assi_metrique", formsemestre.formsemestre_id
+            )
+        ),
     )
     do_abs_notify(formsemestre, etudid, date, nbabs, nbabsjust)
 
@@ -85,6 +93,7 @@ def do_abs_notify(formsemestre: FormSemestre, etudid, date, nbabs, nbabsjust):
         return  # abort
 
     # Vérification fréquence (pour ne pas envoyer de mails trop souvent)
+    # TODO Mettre la fréquence dans les préférences assiduités
     abs_notify_max_freq = sco_preferences.get_preference("abs_notify_max_freq")
     destinations_filtered = []
     for email_addr in destinations:
@@ -174,6 +183,8 @@ def abs_notify_is_above_threshold(etudid, nbabs, nbabsjust, formsemestre_id):
 
     (nbabs > abs_notify_abs_threshold)
     (nbabs - nbabs_last_notified) > abs_notify_abs_increment
+
+    TODO Mettre à jour avec le module assiduité  + fonctionnement métrique
     """
     abs_notify_abs_threshold = sco_preferences.get_preference(
         "abs_notify_abs_threshold", formsemestre_id
diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py
index 2f9624698d22a6b53ac976b33382158fd0eaf3a4..c827207b5518e780b8d5824ebd57a5c0f42aac5e 100644
--- a/app/scodoc/sco_bulletins.py
+++ b/app/scodoc/sco_bulletins.py
@@ -197,7 +197,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
         pid = partition["partition_id"]
         partitions_etud_groups[pid] = sco_groups.get_etud_groups_in_partition(pid)
     # --- Absences
-    I["nbabs"], I["nbabsjust"] = sco_abs.get_abs_count(etudid, nt.sem)
+    I["nbabs"], I["nbabsjust"] = sco_abs.get_assiduites_count(etudid, nt.sem)
 
     # --- Decision Jury
     infos, dpv = etud_descr_situation_semestre(
@@ -487,7 +487,7 @@ def _ue_mod_bulletin(
         )  # peut etre 'NI'
         is_malus = mod["module"]["module_type"] == ModuleType.MALUS
         if bul_show_abs_modules:
-            nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
+            nbabs, nbabsjust = sco_abs.get_assiduites_count(etudid, sem)
             mod_abs = [nbabs, nbabsjust]
             mod["mod_abs_txt"] = scu.fmt_abs(mod_abs)
         else:
diff --git a/app/scodoc/sco_bulletins_json.py b/app/scodoc/sco_bulletins_json.py
index a93f4392e7b08313b2db757a35a11c28fe254219..ec03dec728f78eb859ea62e5827a38526190c14e 100644
--- a/app/scodoc/sco_bulletins_json.py
+++ b/app/scodoc/sco_bulletins_json.py
@@ -297,7 +297,7 @@ def formsemestre_bulletinetud_published_dict(
 
     # --- Absences
     if prefs["bul_show_abs"]:
-        nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
+        nbabs, nbabsjust = sco_abs.get_assiduites_count(etudid, sem)
         d["absences"] = dict(nbabs=nbabs, nbabsjust=nbabsjust)
 
     # --- Décision Jury
diff --git a/app/scodoc/sco_bulletins_xml.py b/app/scodoc/sco_bulletins_xml.py
index e2ad76617bf766c4fcc3a78d5ad82dd2e6073e6e..71845c66627b14b79a15023a50acaeb66a95ccd2 100644
--- a/app/scodoc/sco_bulletins_xml.py
+++ b/app/scodoc/sco_bulletins_xml.py
@@ -63,6 +63,7 @@ from app.scodoc import sco_etud
 from app.scodoc import sco_xml
 from app.scodoc.sco_xml import quote_xml_attr
 
+
 # -------- Bulletin en XML
 # (fonction séparée: n'utilise pas formsemestre_bulletinetud_dict()
 #   pour simplifier le code, mais attention a la maintenance !)
@@ -369,7 +370,7 @@ def make_xml_formsemestre_bulletinetud(
 
     # --- Absences
     if sco_preferences.get_preference("bul_show_abs", formsemestre_id):
-        nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
+        nbabs, nbabsjust = sco_abs.get_assiduites_count(etudid, sem)
         doc.append(Element("absences", nbabs=str(nbabs), nbabsjust=str(nbabsjust)))
     # --- Decision Jury
     if (
diff --git a/app/scodoc/sco_formsemestre_validation.py b/app/scodoc/sco_formsemestre_validation.py
index b2686445ab43a50bd6529b1abaaec1a9f8fcbc37..19af25fe9877bab16e0eb9bdfb7608154a14dc5f 100644
--- a/app/scodoc/sco_formsemestre_validation.py
+++ b/app/scodoc/sco_formsemestre_validation.py
@@ -696,7 +696,7 @@ def formsemestre_recap_parcours_table(
             f"""<td class="rcp_moy">{scu.fmt_note(nt.get_etud_moy_gen(etudid))}</td>"""
         )
         # Absences (nb d'abs non just. dans ce semestre)
-        nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
+        nbabs, nbabsjust = sco_abs.get_assiduites_count(etudid, sem)
         H.append(f"""<td class="rcp_abs">{nbabs - nbabsjust}</td>""")
 
         # UEs
diff --git a/app/scodoc/sco_poursuite_dut.py b/app/scodoc/sco_poursuite_dut.py
index 605cbc07241b088b4e9cc21048eb5a74cb0ea6f1..46f2599e3eeb37d77a28ba1023f1c7ce622976c5 100644
--- a/app/scodoc/sco_poursuite_dut.py
+++ b/app/scodoc/sco_poursuite_dut.py
@@ -107,7 +107,7 @@ def etud_get_poursuite_info(sem, etud):
                                 rangs.append(["rang_" + codeModule, rangModule])
 
                 # Absences
-                nbabs, nbabsjust = sco_abs.get_abs_count(etudid, nt.sem)
+                nbabs, nbabsjust = sco_abs.get_assiduites_count(etudid, nt.sem)
                 if (
                     dec
                     and not sem_descr  # not sem_descr pour ne prendre que le semestre validé le plus récent
diff --git a/app/scodoc/sco_preferences.py b/app/scodoc/sco_preferences.py
index ec21d0095463e6084868a8dcc29d04e4f7b45abe..9656f060c2675076b3694509f43f680886d01c44 100644
--- a/app/scodoc/sco_preferences.py
+++ b/app/scodoc/sco_preferences.py
@@ -653,6 +653,19 @@ class BasePreferences(object):
                     "explanation": "Liste des jours (lun,mar,mer,jeu,ven,sam,dim)",
                 },
             ),
+            (
+                "assi_metrique",
+                {
+                    "initvalue": "1/2 J.",
+                    "input_type": "menu",
+                    "labels": ["1/2 J.", "J.", "H.", "N."],
+                    "allowed_values": ["1/2 J.", "J.", "H.", "N."],
+                    "title": "Métrique de l'assiduité",
+                    "explanation": "Unité affichée dans la fiche étudiante et le bilan\n(J. = journée, H. = heure, N. = nombre)",
+                    "category": "assi",
+                    "only_global" : True,
+                },
+            ),
             # portal
             (
                 "portal_url",
diff --git a/app/scodoc/sco_prepajury.py b/app/scodoc/sco_prepajury.py
index fc369efc0e0920be942fcadd10ed4a4f3f9444ee..8281225c6177134ab841baab08ac623ab4745349 100644
--- a/app/scodoc/sco_prepajury.py
+++ b/app/scodoc/sco_prepajury.py
@@ -139,7 +139,7 @@ def feuille_preparation_jury(formsemestre_id):
                     main_partition_id, ""
                 )
         # absences:
-        e_nbabs, e_nbabsjust = sco_abs.get_abs_count(etud.id, sem)
+        e_nbabs, e_nbabsjust = sco_abs.get_assiduites_count(etud.id, sem)
         nbabs[etud.id] = e_nbabs
         nbabsjust[etud.id] = e_nbabs - e_nbabsjust
 
diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py
index dbcdfffae402276d7b9abbf396a68a80839244ab..73e0569951455bacac047b41c575884aec2d11d8 100644
--- a/app/scodoc/sco_utils.py
+++ b/app/scodoc/sco_utils.py
@@ -251,6 +251,17 @@ def is_period_overlapping(
     return p_deb < i_fin and p_fin > i_deb
 
 
+def translate_assiduites_metric(hr_metric) -> str:
+    if hr_metric == "1/2 J.":
+        return "demi"
+    if hr_metric == "J.":
+        return "journee"
+    if hr_metric == "N.":
+        return "compte"
+    if hr_metric == "H.":
+        return "heure"
+
+
 # Types de modules
 class ModuleType(IntEnum):
     """Code des types de module."""
diff --git a/app/static/js/releve-but.js b/app/static/js/releve-but.js
index c1fac2113b16d73f482e328db337591283a28088..7c6ec4aec9cc800bfb55b0758e5a0462f802f42f 100644
--- a/app/static/js/releve-but.js
+++ b/app/static/js/releve-but.js
@@ -1,70 +1,79 @@
 /* Module par Seb. L. */
 class releveBUT extends HTMLElement {
-	constructor() {
-		super();
-		this.shadow = this.attachShadow({ mode: 'open' });
-
-		/* Config par defaut */
-		this.config = {
-			showURL: true
-		};
-
-		/* Template du module */
-		this.shadow.innerHTML = this.template();
-
-		/* Style du module */
-		const styles = document.createElement('link');
-		styles.setAttribute('rel', 'stylesheet');
-		if (location.href.includes("ScoDoc")) {
-			styles.setAttribute('href', removeLastTwoComponents(getCurrentScriptPath()) + '/css/releve-but.css');	// Scodoc
-		} else {
-			styles.setAttribute('href', '/assets/styles/releve-but.css');		// Passerelle
-		}
-		this.shadow.appendChild(styles);
-	}
-	listeOnOff() {
-		this.parentElement.parentElement.classList.toggle("listeOff");
-		this.parentElement.parentElement.querySelectorAll(".moduleOnOff").forEach(e => {
-			e.classList.remove("moduleOnOff")
-		})
-	}
-	moduleOnOff() {
-		this.parentElement.classList.toggle("moduleOnOff");
-	}
-	goTo() {
-		let module = this.dataset.module;
-		this.parentElement.parentElement.parentElement.parentElement.querySelector("#Module_" + module).scrollIntoView();
-	}
-
-	set setConfig(config) {
-		this.config.showURL = config.showURL ?? this.config.showURL;
-	}
-
-	set showData(data) {
-		// this.showInformations(data);
-		this.showSemestre(data);
-		this.showSynthese(data);
-		this.showEvaluations(data);
-
-		this.showCustom(data);
-
-		this.setOptions(data.options);
-
-		this.shadow.querySelectorAll(".CTA_Liste").forEach(e => {
-			e.addEventListener("click", this.listeOnOff)
-		})
-		this.shadow.querySelectorAll(".ue, .module").forEach(e => {
-			e.addEventListener("click", this.moduleOnOff)
-		})
-		this.shadow.querySelectorAll(":not(.ueBonus)+.syntheseModule").forEach(e => {
-			e.addEventListener("click", this.goTo)
-		})
-
-		this.shadow.children[0].classList.add("ready");
-	}
-
-	template() {
-		return `
+  constructor() {
+    super();
+    this.shadow = this.attachShadow({ mode: "open" });
+
+    /* Config par defaut */
+    this.config = {
+      showURL: true,
+    };
+
+    /* Template du module */
+    this.shadow.innerHTML = this.template();
+
+    /* Style du module */
+    const styles = document.createElement("link");
+    styles.setAttribute("rel", "stylesheet");
+    if (location.href.includes("ScoDoc")) {
+      styles.setAttribute(
+        "href",
+        removeLastTwoComponents(getCurrentScriptPath()) + "/css/releve-but.css"
+      ); // Scodoc
+    } else {
+      styles.setAttribute("href", "/assets/styles/releve-but.css"); // Passerelle
+    }
+    this.shadow.appendChild(styles);
+  }
+  listeOnOff() {
+    this.parentElement.parentElement.classList.toggle("listeOff");
+    this.parentElement.parentElement
+      .querySelectorAll(".moduleOnOff")
+      .forEach((e) => {
+        e.classList.remove("moduleOnOff");
+      });
+  }
+  moduleOnOff() {
+    this.parentElement.classList.toggle("moduleOnOff");
+  }
+  goTo() {
+    let module = this.dataset.module;
+    this.parentElement.parentElement.parentElement.parentElement
+      .querySelector("#Module_" + module)
+      .scrollIntoView();
+  }
+
+  set setConfig(config) {
+    this.config.showURL = config.showURL ?? this.config.showURL;
+  }
+
+  set showData(data) {
+    // this.showInformations(data);
+    this.showSemestre(data);
+    this.showSynthese(data);
+    this.showEvaluations(data);
+
+    this.showCustom(data);
+
+    this.setOptions(data.options);
+
+    this.shadow.querySelectorAll(".CTA_Liste").forEach((e) => {
+      e.addEventListener("click", this.listeOnOff);
+    });
+    this.shadow.querySelectorAll(".ue, .module").forEach((e) => {
+      e.addEventListener("click", this.moduleOnOff);
+    });
+    this.shadow
+      .querySelectorAll(":not(.ueBonus)+.syntheseModule")
+      .forEach((e) => {
+        e.addEventListener("click", this.goTo);
+      });
+
+    this.shadow.children[0].classList.add("ready");
+  }
+
+  template() {
+    return `
 <div>	
 	<div class="wait"></div>
 	<main class="releve">
@@ -140,33 +149,36 @@ class releveBUT extends HTMLElement {
 
 	</main>
 </div>`;
-	}
+  }
 
-	/********************************/
-	/* Informations sur l'étudiant  */
-	/********************************/
-	showInformations(data) {
-		this.shadow.querySelector(".studentPic").src = data.etudiant.photo_url || "default_Student.svg";
+  /********************************/
+  /* Informations sur l'étudiant  */
+  /********************************/
+  showInformations(data) {
+    this.shadow.querySelector(".studentPic").src =
+      data.etudiant.photo_url || "default_Student.svg";
 
-		let output = '';
+    let output = "";
 
-		if (this.config.showURL) {
-			output += `<a href="${data.etudiant.fiche_url}" class=info_etudiant>`;
-		} else {
-			output += `<div class=info_etudiant>`;
-		}
+    if (this.config.showURL) {
+      output += `<a href="${data.etudiant.fiche_url}" class=info_etudiant>`;
+    } else {
+      output += `<div class=info_etudiant>`;
+    }
 
-		output += `
+    output += `
 				<div class=civilite>
 					${this.civilite(data.etudiant.civilite)}
 					${data.etudiant.nom}
 					${data.etudiant.prenom}`;
 
-		if (data.etudiant.date_naissance) {
-			output += ` <div class=dateNaissance>né${(data.etudiant.civilite == "F") ? "e" : ""} le ${this.ISOToDate(data.etudiant.date_naissance)}</div>`;
-		}
+    if (data.etudiant.date_naissance) {
+      output += ` <div class=dateNaissance>né${
+        data.etudiant.civilite == "F" ? "e" : ""
+      } le ${this.ISOToDate(data.etudiant.date_naissance)}</div>`;
+    }
 
-		output += `
+    output += `
 				</div>
 				<div class=numerosEtudiant>
 					Numéro étudiant : ${data.etudiant.code_nip || "~"} - 
@@ -174,46 +186,51 @@ class releveBUT extends HTMLElement {
 				</div>
 				<div>${data.formation.titre}</div>
 		`;
-		if (this.config.showURL) {
-			output += `</a>`;
-		} else {
-			output += `</div>`;
-		}
-
-		this.shadow.querySelector(".infoEtudiant").innerHTML = output;
-	}
-
-	/*******************************/
-	/* Affichage local             */
-	/*******************************/
-	showCustom(data) {
-		this.shadow.querySelector(".custom").innerHTML = data.custom || "";
-	}
-
-	/*******************************/
-	/* Information sur le semestre */
-	/*******************************/
-	showSemestre(data) {
-		let correspondanceCodes = {
-			"ADM": "Admis",
-			"AJD": "Admis par décision de jury",
-			"PASD": "Passage de droit : tout n'est pas validé, mais d'après les règles du BUT, vous passez",
-			"PAS1NCI": "Vous passez par décision de jury mais attention, vous n'avez pas partout le niveau suffisant",
-			"RED": "Ajourné mais autorisé à redoubler",
-			"NAR": "Non admis et non autorisé à redoubler : réorientation",
-			"DEM": "Démission",
-			"ABAN": "Abandon constaté sans lettre de démission",
-			"RAT": "En attente d'un rattrapage",
-			"EXCLU": "Exclusion dans le cadre d'une décision disciplinaire",
-			"DEF": "Défaillance : non évalué par manque d'assiduité",
-			"ABL": "Année blanche"
-		}
-
-		this.shadow.querySelector("#identite_etudiant").innerHTML = ` <a href="${data.etudiant.fiche_url}">${data.etudiant.nomprenom}</a> `;
-		this.shadow.querySelector(".dateInscription").innerHTML += this.ISOToDate(data.semestre.inscription);
-		let output = '';
-		if (!data.options.block_moyenne_generale) {
-			output += `
+    if (this.config.showURL) {
+      output += `</a>`;
+    } else {
+      output += `</div>`;
+    }
+
+    this.shadow.querySelector(".infoEtudiant").innerHTML = output;
+  }
+
+  /*******************************/
+  /* Affichage local             */
+  /*******************************/
+  showCustom(data) {
+    this.shadow.querySelector(".custom").innerHTML = data.custom || "";
+  }
+
+  /*******************************/
+  /* Information sur le semestre */
+  /*******************************/
+  showSemestre(data) {
+    let correspondanceCodes = {
+      ADM: "Admis",
+      AJD: "Admis par décision de jury",
+      PASD: "Passage de droit : tout n'est pas validé, mais d'après les règles du BUT, vous passez",
+      PAS1NCI:
+        "Vous passez par décision de jury mais attention, vous n'avez pas partout le niveau suffisant",
+      RED: "Ajourné mais autorisé à redoubler",
+      NAR: "Non admis et non autorisé à redoubler : réorientation",
+      DEM: "Démission",
+      ABAN: "Abandon constaté sans lettre de démission",
+      RAT: "En attente d'un rattrapage",
+      EXCLU: "Exclusion dans le cadre d'une décision disciplinaire",
+      DEF: "Défaillance : non évalué par manque d'assiduité",
+      ABL: "Année blanche",
+    };
+
+    this.shadow.querySelector(
+      "#identite_etudiant"
+    ).innerHTML = ` <a href="${data.etudiant.fiche_url}">${data.etudiant.nomprenom}</a> `;
+    this.shadow.querySelector(".dateInscription").innerHTML += this.ISOToDate(
+      data.semestre.inscription
+    );
+    let output = "";
+    if (!data.options.block_moyenne_generale) {
+      output += `
 			<div>
 				<div class=enteteSemestre>Moyenne</div><div class=enteteSemestre>${data.semestre.notes.value}</div>
 				<div class=rang>Rang :</div><div class=rang>${data.semestre.rang.value} / ${data.semestre.rang.total}</div>
@@ -222,64 +239,72 @@ class releveBUT extends HTMLElement {
 				<div>Min. promo. :</div><div>${data.semestre.notes.min}</div>
 			</div>
 			`;
-		}
-		output += `
+    }
+    output += `
 			${(() => {
-				if ((!data.semestre.rang.groupes) ||
-					(Object.keys(data.semestre.rang.groupes).length == 0)) {
-					return "";
-				}
-				let output = "";
-				let [idGroupe, dataGroupe] = Object.entries(data.semestre.rang.groupes)[0];
-				output += `<div>
+        if (
+          !data.semestre.rang.groupes ||
+          Object.keys(data.semestre.rang.groupes).length == 0
+        ) {
+          return "";
+        }
+        let output = "";
+        let [idGroupe, dataGroupe] = Object.entries(
+          data.semestre.rang.groupes
+        )[0];
+        output += `<div>
 					<div class=enteteSemestre>${data.semestre.groupes[0]?.group_name}</div><div></div>
 					<div class=rang>Rang :</div><div class=rang>${dataGroupe.value} / ${dataGroupe.total}</div>
 				</div>`;
-				// <div>Max. promo. :</div><div>${dataGroupe.max || "-"}</div>
-				// <div>Moy. promo. :</div><div>${dataGroupe.moy || "-"}</div>
-				// <div>Min. promo. :</div><div>${dataGroupe.min || "-"}</div>
-				return output;
-			})()}
+        // <div>Max. promo. :</div><div>${dataGroupe.max || "-"}</div>
+        // <div>Moy. promo. :</div><div>${dataGroupe.moy || "-"}</div>
+        // <div>Min. promo. :</div><div>${dataGroupe.min || "-"}</div>
+        return output;
+      })()}
 			<div class=absencesRecap>
-				<div class=enteteSemestre>Absences</div><div class=enteteSemestre>1/2 jour.</div>
+				<div class=enteteSemestre>Absences</div><div class=enteteSemestre>${
+          data.semestre.absences?.metrique ?? "1/2 jour."
+        }</div>
 				<div class=abs>Non justifiées</div>
 				<div>${data.semestre.absences?.injustifie ?? "-"}</div>
 				<div class=abs>Total</div><div>${data.semestre.absences?.total ?? "-"}</div>
 			</div>`;
-		if (data.semestre.decision_rcue?.length) {
-			output += `
+    if (data.semestre.decision_rcue?.length) {
+      output += `
 			<div>
 				<div class=enteteSemestre>RCUE</div><div></div>
 				${(() => {
-					let output = "";
-					data.semestre.decision_rcue.forEach(competence => {
-						output += `<div class=competence>${competence.niveau.competence.titre}</div><div>${competence.code}</div>`;
-					})
-					return output;
-				})()}
+          let output = "";
+          data.semestre.decision_rcue.forEach((competence) => {
+            output += `<div class=competence>${competence.niveau.competence.titre}</div><div>${competence.code}</div>`;
+          });
+          return output;
+        })()}
 				</div>
-			</div>`
-		}
-		if (data.semestre.decision_ue?.length) {
-			output += `
+			</div>`;
+    }
+    if (data.semestre.decision_ue?.length) {
+      output += `
 			<div>
 				<div class=enteteSemestre>UE</div><div></div>
 				${(() => {
-					let output = "";
-					data.semestre.decision_ue.forEach(ue => {
-						output += `<div class=competence>${ue.acronyme}</div><div>${ue.code}</div>`;
-					})
-					return output;
-				})()}
+          let output = "";
+          data.semestre.decision_ue.forEach((ue) => {
+            output += `<div class=competence>${ue.acronyme}</div><div>${ue.code}</div>`;
+          });
+          return output;
+        })()}
 				</div>
-			</div>`
-		}
+			</div>`;
+    }
 
-		output += `
+    output += `
 			<a class=photo href="${data.etudiant.fiche_url}">
-				<img src="${data.etudiant.photo_url || "default_Student.svg"}" alt="photo de l'étudiant" title="fiche de l'étudiant" height="120" border="0">
+				<img src="${
+          data.etudiant.photo_url || "default_Student.svg"
+        }" alt="photo de l'étudiant" title="fiche de l'étudiant" height="120" border="0">
 			</a>`;
-		/*${data.semestre.groupes.map(groupe => {
+    /*${data.semestre.groupes.map(groupe => {
 			return `
 					<div>
 						<div class=enteteSemestre>Groupe</div><div class=enteteSemestre>${groupe.nom}</div>
@@ -291,37 +316,42 @@ class releveBUT extends HTMLElement {
 				`;
 			}).join("")
 		}*/
-		this.shadow.querySelector(".infoSemestre").innerHTML = output;
-
+    this.shadow.querySelector(".infoSemestre").innerHTML = output;
 
-		/*if(data.semestre.decision_annee?.code){
+    /*if(data.semestre.decision_annee?.code){
 			this.shadow.querySelector(".decision_annee").innerHTML = "Décision année : " + data.semestre.decision_annee.code + " - " + correspondanceCodes[data.semestre.decision_annee.code];
 		}*/
 
-		this.shadow.querySelector(".decision").innerHTML = data.semestre.situation || "";
-		/*if (data.semestre.decision?.code) {
+    this.shadow.querySelector(".decision").innerHTML =
+      data.semestre.situation || "";
+    /*if (data.semestre.decision?.code) {
 			this.shadow.querySelector(".decision").innerHTML = "Décision jury: " + (data.semestre.decision?.code || "");
 		}*/
-		this.shadow.querySelector("#ects_tot").innerHTML = "ECTS&nbsp;:&nbsp;" + (data.semestre.ECTS?.acquis ?? "-") + "&nbsp;/&nbsp;" + (data.semestre.ECTS?.total ?? "-");
-	}
-
-	/*******************************/
-	/* Synthèse                    */
-	/*******************************/
-	showSynthese(data) {
-		let output = ``;
-		/* Fusion et tri des UE et UE capitalisées */
-		let fusionUE = [
-			...Object.entries(data.ues),
-			...Object.entries(data.ues_capitalisees)
-		].sort((a, b) => {
-			return a[1].numero - b[1].numero
-		});
-
-		/* Affichage */
-		fusionUE.forEach(([ue, dataUE]) => {
-			if (dataUE.type == 1) {		// UE Sport / Bonus
-				output += `
+    this.shadow.querySelector("#ects_tot").innerHTML =
+      "ECTS&nbsp;:&nbsp;" +
+      (data.semestre.ECTS?.acquis ?? "-") +
+      "&nbsp;/&nbsp;" +
+      (data.semestre.ECTS?.total ?? "-");
+  }
+
+  /*******************************/
+  /* Synthèse                    */
+  /*******************************/
+  showSynthese(data) {
+    let output = ``;
+    /* Fusion et tri des UE et UE capitalisées */
+    let fusionUE = [
+      ...Object.entries(data.ues),
+      ...Object.entries(data.ues_capitalisees),
+    ].sort((a, b) => {
+      return a[1].numero - b[1].numero;
+    });
+
+    /* Affichage */
+    fusionUE.forEach(([ue, dataUE]) => {
+      if (dataUE.type == 1) {
+        // UE Sport / Bonus
+        output += `
 					<div>
 						<div class="ue ueBonus">
 							<h3>Bonus</h3>
@@ -330,52 +360,60 @@ class releveBUT extends HTMLElement {
 						${this.ueSport(dataUE.modules)}
 					</div>
 				`;
-			} else {
-				output += `
+      } else {
+        output += `
 					<div>
 						<div class="ue ${dataUE.date_capitalisation ? "capitalisee" : ""}">
 							<h3>
-								${ue}${(dataUE.titre) ? " - " + dataUE.titre : ""}
+								${ue}${dataUE.titre ? " - " + dataUE.titre : ""}
 							</h3>
 							<div>
-								<div class=moyenne>Moyenne&nbsp;:&nbsp;${dataUE.moyenne?.value || dataUE.moyenne || "-"}</div>
-								<div class=ue_rang>Rang&nbsp;:&nbsp;${dataUE.moyenne?.rang}&nbsp;/&nbsp;${dataUE.moyenne?.total}</div>
+								<div class=moyenne>Moyenne&nbsp;:&nbsp;${
+                  dataUE.moyenne?.value || dataUE.moyenne || "-"
+                }</div>
+								<div class=ue_rang>Rang&nbsp;:&nbsp;${dataUE.moyenne?.rang}&nbsp;/&nbsp;${
+          dataUE.moyenne?.total
+        }</div>
 								<div class=info>`;
-				if (!dataUE.date_capitalisation) {
-					output += `		Bonus&nbsp;:&nbsp;${dataUE.bonus || 0}&nbsp;- 
+        if (!dataUE.date_capitalisation) {
+          output += `		Bonus&nbsp;:&nbsp;${dataUE.bonus || 0}&nbsp;- 
 									Malus&nbsp;:&nbsp;${dataUE.malus || 0}`;
-				} else {
-					output += `		le ${this.ISOToDate(dataUE.date_capitalisation.split("T")[0])} <a href="${dataUE.bul_orig_url}">dans ce semestre</a>`;
-				}
-
-				output += `			<span class=ects>&nbsp;-
-										ECTS&nbsp;:&nbsp;${dataUE.ECTS?.acquis ?? "-"}&nbsp;/&nbsp;${dataUE.ECTS?.total ?? "-"}
+        } else {
+          output += `		le ${this.ISOToDate(
+            dataUE.date_capitalisation.split("T")[0]
+          )} <a href="${dataUE.bul_orig_url}">dans ce semestre</a>`;
+        }
+
+        output += `			<span class=ects>&nbsp;-
+										ECTS&nbsp;:&nbsp;${dataUE.ECTS?.acquis ?? "-"}&nbsp;/&nbsp;${
+          dataUE.ECTS?.total ?? "-"
+        }
 									</span>
 								</div>
 							</div>`;
-				/*<div class=absences>
+        /*<div class=absences>
 					<div>Abs&nbsp;N.J.</div><div>${dataUE.absences?.injustifie || 0}</div>
 					<div>Total</div><div>${dataUE.absences?.total || 0}</div>
 				</div>*/
-				output += "</div>";
-
-				if (!dataUE.date_capitalisation) {
-					output +=
-						this.synthese(data, dataUE.ressources) +
-						this.synthese(data, dataUE.saes);
-				}
-
-				output += "</div>";
-			}
-		});
-		this.shadow.querySelector(".synthese").innerHTML = output;
-	}
-	synthese(data, modules) {
-		let output = "";
-		Object.entries(modules).forEach(([module, dataModule]) => {
-			let titre = data.ressources[module]?.titre || data.saes[module]?.titre;
-			//let url = data.ressources[module]?.url || data.saes[module]?.url;
-			output += `
+        output += "</div>";
+
+        if (!dataUE.date_capitalisation) {
+          output +=
+            this.synthese(data, dataUE.ressources) +
+            this.synthese(data, dataUE.saes);
+        }
+
+        output += "</div>";
+      }
+    });
+    this.shadow.querySelector(".synthese").innerHTML = output;
+  }
+  synthese(data, modules) {
+    let output = "";
+    Object.entries(modules).forEach(([module, dataModule]) => {
+      let titre = data.ressources[module]?.titre || data.saes[module]?.titre;
+      //let url = data.ressources[module]?.url || data.saes[module]?.url;
+      output += `
 				<div class=syntheseModule data-module="${module.replace(/[^a-zA-Z0-9]/g, "")}">
 					<div>${module}&nbsp;- ${titre}</div>
 					<div>
@@ -384,14 +422,14 @@ class releveBUT extends HTMLElement {
 					</div>
 				</div>
 			`;
-		})
-		return output;
-	}
-	ueSport(modules) {
-		let output = "";
-		Object.values(modules).forEach((module) => {
-			Object.values(module.evaluations).forEach((evaluation) => {
-				output += `
+    });
+    return output;
+  }
+  ueSport(modules) {
+    let output = "";
+    Object.values(modules).forEach((module) => {
+      Object.values(module.evaluations).forEach((evaluation) => {
+        output += `
 					<div class=syntheseModule>
 						<div>${module.titre} - ${evaluation.description || "Note"}</div>
 						<div>
@@ -400,27 +438,31 @@ class releveBUT extends HTMLElement {
 						</div>
 					</div>
 				`;
-			})
-		})
-		return output;
-	}
-
-	/*******************************/
-	/* Evaluations                 */
-	/*******************************/
-	showEvaluations(data) {
-		this.shadow.querySelector(".evaluations").innerHTML = this.module(data.ressources);
-		this.shadow.querySelector(".sae").innerHTML += this.module(data.saes);
-	}
-	module(module) {
-		let output = "";
-		Object.entries(module).forEach(([numero, content]) => {
-			output += `
+      });
+    });
+    return output;
+  }
+
+  /*******************************/
+  /* Evaluations                 */
+  /*******************************/
+  showEvaluations(data) {
+    this.shadow.querySelector(".evaluations").innerHTML = this.module(
+      data.ressources
+    );
+    this.shadow.querySelector(".sae").innerHTML += this.module(data.saes);
+  }
+  module(module) {
+    let output = "";
+    Object.entries(module).forEach(([numero, content]) => {
+      output += `
 				<div id="Module_${numero.replace(/[^a-zA-Z0-9]/g, "")}">
 					<div class=module>
 						<h3>${this.URL(content.url, `${numero} - ${content.titre}`)}</h3>
 						<div>
-							<div class=moyenne>Moyenne&nbsp;indicative&nbsp;:&nbsp;${content.moyenne.value}</div>
+							<div class=moyenne>Moyenne&nbsp;indicative&nbsp;:&nbsp;${
+                content.moyenne.value
+              }</div>
 							<div class=info>
 								Classe&nbsp;:&nbsp;${content.moyenne.moy}&nbsp;- 
 								Max&nbsp;:&nbsp;${content.moyenne.max}&nbsp;-
@@ -435,14 +477,14 @@ class releveBUT extends HTMLElement {
 					${this.evaluation(content.evaluations)}
 				</div>
 			`;
-		})
-		return output;
-	}
-
-	evaluation(evaluations) {
-		let output = "";
-		evaluations.forEach((evaluation) => {
-			output += `
+    });
+    return output;
+  }
+
+  evaluation(evaluations) {
+    let output = "";
+    evaluations.forEach((evaluation) => {
+      output += `
 				<div class=eval>
 					<div>${this.URL(evaluation.url, evaluation.description || "Évaluation")}</div>
 					<div>
@@ -454,52 +496,55 @@ class releveBUT extends HTMLElement {
 						<div>Max. promo.</div><div>${evaluation.note.max}</div>
 						<div>Moy. promo.</div><div>${evaluation.note.moy}</div>
 						<div>Min. promo.</div><div>${evaluation.note.min}</div>
-						${Object.entries(evaluation.poids).map(([UE, poids]) => {
-				return `
+						${Object.entries(evaluation.poids)
+              .map(([UE, poids]) => {
+                return `
 								<div>Poids ${UE}</div>
 								<div>${poids}</div>
 							`;
-			}).join("")}
+              })
+              .join("")}
 					</div>
 				</div>
 			`;
-		})
-		return output;
-	}
-
-	/********************/
-	/* Options          */
-	/********************/
-	setOptions(options) {
-		Object.entries(options).forEach(([option, value]) => {
-			if (value === false) {
-				this.shadow.children[0].classList.add(option.replace("show", "hide"));
-			}
-		});
-	}
-
-
-	/********************/
-	/* Fonctions d'aide */
-	/********************/
-	URL(href, content) {
-		if (this.config.showURL) {
-			return `<a href=${href}>${content}</a>`;
-		} else {
-			return content;
-		}
-	}
-	civilite(txt) {
-		switch (txt) {
-			case "M": return "M.";
-			case "F": return "Mme";
-			default: return "";
-		}
-	}
-
-	ISOToDate(ISO) {
-		return ISO.split("-").reverse().join("/");
-	}
-
+    });
+    return output;
+  }
+
+  /********************/
+  /* Options          */
+  /********************/
+  setOptions(options) {
+    Object.entries(options).forEach(([option, value]) => {
+      if (value === false) {
+        this.shadow.children[0].classList.add(option.replace("show", "hide"));
+      }
+    });
+  }
+
+  /********************/
+  /* Fonctions d'aide */
+  /********************/
+  URL(href, content) {
+    if (this.config.showURL) {
+      return `<a href=${href}>${content}</a>`;
+    } else {
+      return content;
+    }
+  }
+  civilite(txt) {
+    switch (txt) {
+      case "M":
+        return "M.";
+      case "F":
+        return "Mme";
+      default:
+        return "";
+    }
+  }
+
+  ISOToDate(ISO) {
+    return ISO.split("-").reverse().join("/");
+  }
 }
-customElements.define('releve-but', releveBUT);
+customElements.define("releve-but", releveBUT);
diff --git a/app/templates/sidebar.j2 b/app/templates/sidebar.j2
index b579a1f03275c9bbe64ed2e3f79e3af187eab829..19c2199ff8b6214e2832a1cd23674ad8ab922e85 100755
--- a/app/templates/sidebar.j2
+++ b/app/templates/sidebar.j2
@@ -55,7 +55,7 @@
         <b>Absences</b>
         {% if sco.etud_cur_sem %}
         <span title="absences du {{ sco.etud_cur_sem['date_debut'] }}
-        au {{ sco.etud_cur_sem['date_fin'] }}">(1/2 j.)
+        au {{ sco.etud_cur_sem['date_fin'] }}">({{sco.prefs["assi_metrique"]}})
             <br />{{sco.nbabsjust}} J., {{sco.nbabsnj}} N.J.</span>
         {% endif %}
         <ul>
diff --git a/app/views/__init__.py b/app/views/__init__.py
index 89d3418b792db20c24fefc8a974cd84ce99edfd2..a0edb6ba83cae35aff8b876262f7a6e79c4d6493 100644
--- a/app/views/__init__.py
+++ b/app/views/__init__.py
@@ -72,10 +72,13 @@ class ScoData:
             ins = self.etud.inscription_courante()
             if ins:
                 self.etud_cur_sem = ins.formsemestre
-                self.nbabs, self.nbabsjust = sco_abs.get_abs_count_in_interval(
+                self.nbabs, self.nbabsjust = sco_abs.get_assiduites_count_in_interval(
                     etud.id,
                     self.etud_cur_sem.date_debut.isoformat(),
                     self.etud_cur_sem.date_fin.isoformat(),
+                    scu.translate_assiduites_metric(
+                        sco_preferences.get_preference("assi_metrique")
+                    ),
                 )
                 self.nbabsnj = self.nbabs - self.nbabsjust
             else: