diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py
index 5541d178074ac64600d64a71dc1fb02af2c2395f..ba08fed489c6a1ea49cb9f1a99f5b555b5382144 100644
--- a/app/models/formsemestre.py
+++ b/app/models/formsemestre.py
@@ -873,7 +873,7 @@ class FormSemestre(db.Model):
             descr_sem += " " + self.modalite
         return descr_sem
 
-    def get_abs_count(self, etudid):
+    def get_abs_count(self, etudid) -> tuple[int, int, int]:
         """Les comptes d'absences de cet étudiant dans ce semestre:
         tuple (nb abs non just, nb abs justifiées, nb abs total)
         Utilise un cache.
diff --git a/app/scodoc/sco_cost_formation.py b/app/scodoc/sco_cost_formation.py
index 5c167e6bc513e3133a2923026d95da16fa0fcc43..649623a6c33bd3a247fd69a99c678670d583e3c3 100644
--- a/app/scodoc/sco_cost_formation.py
+++ b/app/scodoc/sco_cost_formation.py
@@ -30,17 +30,18 @@
 
    (coût théorique en heures équivalent TD)
 """
-from flask import request
+from flask import request, Response
 
 from app.models import FormSemestre
-from app.scodoc.gen_tables import GenTable
 from app.scodoc import sco_preferences
+from app.scodoc.gen_tables import GenTable
+from app.scodoc.sco_exceptions import ScoValueError
 import app.scodoc.sco_utils as scu
 import sco_version
 
 
 def formsemestre_table_estim_cost(
-    formsemestre_id,
+    formsemestre: FormSemestre,
     n_group_td=1,
     n_group_tp=1,
     coef_tp=1,
@@ -55,8 +56,6 @@ def formsemestre_table_estim_cost(
     peut conduire à une sur-estimation du coût s'il y a des modules optionnels
     (dans ce cas, retoucher le tableau excel exporté).
     """
-    formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
-
     rows = []
     for modimpl in formsemestre.modimpls:
         rows.append(
@@ -76,14 +75,14 @@ def formsemestre_table_estim_cost(
             + coef_cours * row["heures_cours"]
             + coef_tp * row["heures_tp"]
         )
-    sum_cours = sum([t["heures_cours"] for t in rows])
-    sum_td = sum([t["heures_td"] for t in rows])
-    sum_tp = sum([t["heures_tp"] for t in rows])
+    sum_cours = sum(t["heures_cours"] for t in rows)
+    sum_td = sum(t["heures_td"] for t in rows)
+    sum_tp = sum(t["heures_tp"] for t in rows)
     sum_heqtd = sum_td + coef_cours * sum_cours + coef_tp * sum_tp
-    assert abs(sum([t["HeqTD"] for t in rows]) - sum_heqtd) < 0.01, "%s != %s" % (
-        sum([t["HeqTD"] for t in rows]),
-        sum_heqtd,
-    )
+    # assert abs(sum(t["HeqTD"] for t in rows) - sum_heqtd) < 0.01, "%s != %s" % (
+    #     sum(t["HeqTD"] for t in rows),
+    #     sum_heqtd,
+    # )
 
     rows.append(
         {
@@ -117,7 +116,7 @@ def formsemestre_table_estim_cost(
         ),
         rows=rows,
         html_sortable=True,
-        preferences=sco_preferences.SemPreferences(formsemestre_id),
+        preferences=sco_preferences.SemPreferences(formsemestre.id),
         html_class="table_leftalign table_listegroupe",
         xls_before_table=[
             [formsemestre.titre_annee()],
@@ -146,47 +145,45 @@ def formsemestre_table_estim_cost(
     return tab
 
 
+# view
 def formsemestre_estim_cost(
-    formsemestre_id,
-    n_group_td=1,
-    n_group_tp=1,
-    coef_tp=1,
-    coef_cours=1.5,
+    formsemestre_id: int,
+    n_group_td: int | str = 1,
+    n_group_tp: int | str = 1,
+    coef_tp: float | str = 1.0,
+    coef_cours: float | str = 1.5,
     fmt="html",
-):
+) -> str | Response:
     """Page (formulaire) estimation coûts"""
+    try:
+        n_group_td = int(n_group_td)
+        n_group_tp = int(n_group_tp)
+        coef_tp = float(coef_tp)
+        coef_cours = float(coef_cours)
+    except ValueError as exc:
+        raise ScoValueError("paramètre invalide: utiliser des nombres") from exc
 
-    n_group_td = int(n_group_td)
-    n_group_tp = int(n_group_tp)
-    coef_tp = float(coef_tp)
-    coef_cours = float(coef_cours)
+    formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
 
     tab = formsemestre_table_estim_cost(
-        formsemestre_id,
+        formsemestre,
         n_group_td=n_group_td,
         n_group_tp=n_group_tp,
         coef_tp=coef_tp,
         coef_cours=coef_cours,
     )
-    h = """
-    <form name="f" method="get" action="%s">
-    <input type="hidden" name="formsemestre_id" value="%s"></input>
-    Nombre de groupes de TD: <input type="text" name="n_group_td" value="%s" onchange="document.f.submit()"/><br>
-    Nombre de groupes de TP: <input type="text" name="n_group_tp" value="%s" onchange="document.f.submit()"/>
-    &nbsp;Coefficient heures TP: <input type="text" name="coef_tp" value="%s" onchange="document.f.submit()"/>
+    tab.html_before_table = f"""
+    <form name="f" method="get" action="{request.base_url}">
+    <input type="hidden" name="formsemestre_id" value="{formsemestre.id}"></input>
+    Nombre de groupes de TD: <input type="text" name="n_group_td" value="{n_group_td}" onchange="document.f.submit()"/><br>
+    Nombre de groupes de TP: <input type="text" name="n_group_tp" value="{n_group_tp}" onchange="document.f.submit()"/>
+    &nbsp;Coefficient heures TP: <input type="text" name="coef_tp" value="{coef_tp}" onchange="document.f.submit()"/>
     <br>
     </form>
-    """ % (
-        request.base_url,
-        formsemestre_id,
-        n_group_td,
-        n_group_tp,
-        coef_tp,
-    )
-    tab.html_before_table = h
+    """
     tab.base_url = "%s?formsemestre_id=%s&n_group_td=%s&n_group_tp=%s&coef_tp=%s" % (
         request.base_url,
-        formsemestre_id,
+        formsemestre.id,
         n_group_td,
         n_group_tp,
         coef_tp,