"""Chapitre 10 — vérification des chiffres cités dans le texte, les tableaux et les solutions.""" import sys, pathlib, datetime as dt import pytest sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases")) import ch10_controle_rapport as cr import ch10_matrice_normes as mn @pytest.fixture(scope="module") def out(): return cr.main(verbose=False) @pytest.fixture(scope="module") def mat(): return mn.main(verbose=False) # --- contrôle lexical des douze éléments (tableau 10.7, figure grille) ------------------------------------------- def test_douze_elements_definis(): assert [e.no for e in cr.ELEMENTS] == list(range(1, 13)) assert len({e.cle for e in cr.ELEMENTS}) == 12 def test_extrait_complet_et_lacunaires(out): assert out["manquants"][("D", "strict")] == [] and out["taux"][("D", "strict")] == 1.0 assert out["manquants"][("A", "strict")] == [1, 2, 4, 5, 6, 7, 9, 10, 11, 12] assert out["manquants"][("A", "souple")] == [2, 4, 5, 7, 9, 10, 11] assert out["manquants"][("B", "strict")] == [6, 10] assert out["manquants"][("C", "strict")] == [2, 3, 4, 9, 11, 12] assert out["taux"][("A", "strict")] == pytest.approx(2 / 12) assert out["taux"][("B", "strict")] == pytest.approx(10 / 12) assert out["taux"][("C", "strict")] == pytest.approx(0.5) def test_lexique_souple_jamais_plus_severe(out): for nom in cr.EXTRAITS: s, l = out["manquants"][(nom, "strict")], out["manquants"][(nom, "souple")] assert set(l) <= set(s) def test_taux_conformite_identite(): res = {e.cle: (e.no % 2 == 0, None) for e in cr.ELEMENTS} assert cr.taux_conformite(res) == pytest.approx(0.5) assert cr.manquants(res) == [1, 3, 5, 7, 9, 11] def test_controle_insensible_aux_accents_et_casse(): r1 = cr.controler("DATE D'ÉVALUATION : 1er septembre 2026. J'ATTESTE par la présente.") r2 = cr.controler("date d'evaluation : 1er septembre 2026. j'atteste par la presente.") assert r1["date"][0] and r1["attestation"][0] and r1 == r2 # --- probabilité de détection par révisions indépendantes (tableau 10.8) --------------------------------------- def test_p_detection(out): assert cr.p_detection(0.5, 1) == 0.5 and cr.p_detection(0.5, 2) == 0.75 and cr.p_detection(0.5, 3) == 0.875 assert cr.p_detection(0.7, 2) == pytest.approx(0.91) and cr.p_detection(0.7, 3) == pytest.approx(0.973) assert cr.p_detection(0.9, 2) == pytest.approx(0.99) and cr.p_detection(0.9, 3) == pytest.approx(0.999) assert cr.p_detection(0.0, 5) == 0.0 and cr.p_detection(1.0, 1) == 1.0 vals = [cr.p_detection(0.7, n) for n in range(1, 6)] assert all(b > a for a, b in zip(vals, vals[1:])) # --- hypothèse extraordinaire : incidence et espérance (section 10.2, exercice 10.5) --------------------------- def test_incidence_hypothese(out): dv, dr = out["incidence_B"] assert dv == 37000 and dr == pytest.approx(0.0643, abs=0.0001) assert out["esperance_B"] == pytest.approx(597200) assert cr.esperance_sous_incertitude(1.0, 612000, 575000) == 612000 assert cr.esperance_sous_incertitude(0.0, 612000, 575000) == 575000 # --- échéances réglementaires (exercice 10.2) ----------------------------------------------------------------- def test_conservation_et_formation(out): assert out["conservation"] == dt.date(2031, 11, 15) f = out["formation"] assert f["deficit_total"] == 0 and f["deficit_ethique"] == 1 and f["report_suivant"] == 1 and not f["conforme"] g = out["formation_b"] assert g["conforme"] and g["report_suivant"] == 3 h = cr.bilan_formation(heures_total=20, heures_ethique=3, report_anterieur=8) # report plafonné à 5 h assert h["deficit_total"] == 5 and not h["conforme"] # --- statistiques de la matrice des dispositions (tableau 10.9, figure matrice) --------------------------------- def test_matrice_normes(mat): st = mat["stats"] assert st["n"] == 100 and st["n_domaines"] == 17 and st["n_chapitres_couverts"] == 28 assert st["par_domaine"]["OEAQ-NPP"] == 15 and st["par_domaine"]["OEAQ-Déontologie"] == 14 and st["par_domaine"]["OEAQ-Règlements"] == 12 assert st["par_domaine"]["IVS"] == 11 and st["par_domaine"]["CUSPAP"] == 8 and st["par_domaine"]["IAAO"] == 6 assert st["par_famille"]["Cadres hors Québec"] == 27 and st["par_famille"]["Finance et prudentiel"] == 20 assert st["n_oeaq"] == 43 and st["n_ch10"] == 51 assert st["par_chapitre"]["ch10"] == 51 and st["par_chapitre"]["ch31"] == 15 and st["par_chapitre"]["ch20"] == 14 assert st["moy_chapitres_par_disposition"] == pytest.approx(1.70) and st["max_chapitres_par_disposition"] == 4 assert st["part_multi_chapitres"] == pytest.approx(0.51) assert st["par_famille"].sum() == 100 def test_chronologie(mat): ch = mat["chronologie"].set_index("cadre") assert ch.loc["CUSPAP 2024 (Canada)", "en_vigueur_2026_10_31"] == False assert ch.loc["IVS 2028 — projet (exposure draft)", "en_vigueur_2026_10_31"] == False assert ch["en_vigueur_2026_10_31"].sum() == 8 assert ch.loc["IVS 2025 (IVSC)", "delai_publication_effet_jours"] == 366 assert ch.loc["RICS Red Book Global (2025)", "delai_publication_effet_jours"] == 51 assert ch.loc["NPP de l'OEAQ (Québec)", "effet"] == dt.date(2024, 2, 1) assert ch.loc["CUSPAP 2026 (Canada)", "effet"] == dt.date(2026, 4, 1)