spb/traite-evaluation-qc
Public
1"""Tests du chapitre 45 (cas 1, maison unifamiliale à Gatineau — DONNÉES SYNTHÉTIQUES) : le dossier ne doit reprendre aucun chiffre2différent des chapitres de méthode (ch. 9, 11, 12, 16, 17, 18, 20, 21, 24, 25) ; identités du financement de l'acheteur ;3chiffres cités dans le texte et les solutions. Tolérances déclarées : montants cités au dollar ±1 $ ; taux ±1e-6 ; identités ±1e-6."""4import sys, pathlib, math5import numpy as np, pytest6sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases"))7from traite import finance as fin, cas18import ch45_dossier as ch9import ch17_ajustements as c1710import ch20_reconciliation as c2011import ch24_depreciation as c2412import ch12_mesurage as c1213import ch09_ratios as c09141516@pytest.fixture(scope="module")17def D():18 return ch.dossier()192021@pytest.fixture(scope="module")22def E(D):23 return ch.exercices(D)242526# ----------------------------------------------------------------------------- héritages : égalité stricte avec les chapitres de méthode27def test_selection_ch16(D):28 assert D["etapes16"] == {"candidats_total": 600, "apres_delai": 236, "apres_rayon": 40, "apres_type": 27, "apres_superficie": 16}29 assert list(D["comparables"]["id_vente"]) == ["G0574", "G0404", "G0468", "G0477", "G0575"]30 assert D["indication_brute16"] == pytest.approx(551_029, abs=1)313233def test_grille_ch17(D):34 b = c17.base(); rec = c17.reconcilier(b)35 assert np.allclose(D["adj17"]["prix_ajuste"].values, b["adj"]["prix_ajuste"].values)36 assert D["adj17"]["prix_ajuste"].round(0).tolist() == [640247, 541222, 572793, 568853, 584862]37 assert np.allclose(D["poids17"], rec["poids_inverse_brut"]) and np.round(D["poids17"], 3).tolist() == [0.179, 0.327, 0.202, 0.159, 0.133]38 assert D["V_comp"] == pytest.approx(rec["inverse_brut"]["valeur_ponderee"]) and D["V_comp"] == pytest.approx(575_565, abs=1)39 assert D["rec17"]["egal"]["valeur_ponderee"] == pytest.approx(581_595, abs=1) and D["rec17"]["egal"]["mediane"] == pytest.approx(572_793, abs=1)404142def test_hedonique_ch18(D):43 t = cas1.tout()44 assert D["V_hed"] == pytest.approx(t["prediction_hedonique"]) and D["V_hed"] == pytest.approx(637_953, abs=1)45 assert D["ip95"][0] == pytest.approx(539_682, abs=1) and D["ip95"][1] == pytest.approx(749_087, abs=1)46 assert D["ip80"][0] == pytest.approx(571_192, abs=1) and D["ip80"][1] == pytest.approx(707_764, abs=1)47 assert D["ic95_valeur"][0] == pytest.approx(625_586, abs=5) and D["ic95_valeur"][1] == pytest.approx(650_596, abs=5)48 assert D["pred18"]["facteur_duan"] == pytest.approx(1.00335, abs=1e-5)495051def test_cout_ch21_ch24_ch25(D):52 assert D["cn"]["total"] == pytest.approx(397_567.33, abs=0.01) and D["cn"]["direct"] == pytest.approx(298_826.17, abs=0.01)53 assert D["cn"]["indirects"] == pytest.approx(62_598.68, abs=0.01) and D["cn"]["incitatif"] == pytest.approx(36_142.49, abs=0.01)54 assert D["V_L"] == 277_000 and D["D_ext"] == pytest.approx(87_461.05, abs=0.01) and D["taux_ext"] == pytest.approx(0.22, abs=1e-6)55 assert D["D_fonc"] == pytest.approx(7_605.31, abs=0.01) and D["D_tot"] == pytest.approx(95_066.36, abs=0.01) and D["taux_tot"] == pytest.approx(0.2391, abs=5e-5)56 assert D["V_cout"] == pytest.approx(579_500.97, abs=0.01) and D["V_cout_arrondie"] == 580_00057 assert D["V_cout"] == pytest.approx(D["V_L"] + D["CN"] - D["D_ext"] - D["D_fonc"])58 assert D["V_ventilee"] == pytest.approx(445_872.14, abs=0.01)59 assert D["comp25"]["reconciliation"]["valeur_ponderee"] == pytest.approx(267_388, abs=1)60 assert D["comp25"]["prediction_hedonique"] == pytest.approx(253_412, abs=1)61 assert D["extr25"]["mediane"] == pytest.approx(276_954, abs=1) and D["extr25"]["n"] == 13 and D["extr25"]["retenue"] == 277_000626364def test_inspection_ch11_mesurage_ch12(D):65 dv = D["dv11"]66 assert dv["age_effectif_structure"] == pytest.approx(29, abs=1e-9) and dv["curable"] == pytest.approx(10_479.98, abs=0.01)67 assert dv["total"] == pytest.approx(221_089.88, abs=0.01) and dv["taux"] == pytest.approx(0.5561, abs=5e-5)68 s = D["sup12"]69 assert s["aire_sol_m2"] == pytest.approx(95.04) and s["aire_etages_m2"] == pytest.approx(190.08) and s["habitable_m2"] == 165.070 assert s["nette_m2"] == pytest.approx(159.9, abs=0.05) and s["lot_m2"] == pytest.approx(620, abs=0.05) and s["garage_m2"] == pytest.approx(22.33, abs=0.01)71 assert len(D["fiche"]) == 20727374def test_role_taxes_ch09(D):75 assert D["role"] == 468_000 and D["valeur_reelle_2024"] == 573_00076 assert D["PM_fictif"] == pytest.approx(0.9244, abs=5e-5) and D["F_fictif"] == pytest.approx(1.0818, abs=5e-5)77 assert D["taxes"]["total"] == pytest.approx(4_076.01, abs=0.01)78 assert D["ratio_sujet_role"] == pytest.approx(0.8168, abs=5e-5)798081def test_reconciliation_ch20(D):82 r = c20.tout(); cb = r["combinaison"]83 assert D["conclusion"] == cb["conclusion"] == 605_000 and tuple(D["fourchette"]) == (575_000, 640_000)84 assert D["V_raisonnee"] == pytest.approx(604_229.92, abs=0.01)85 assert D["V_raisonnee"] == pytest.approx(0.40 * D["V_comp"] + 0.45 * D["V_hed"] + 0.15 * D["V_cout"])86 assert D["indications"]["cout"] == pytest.approx(D["V_cout"])87 assert D["ecarts_types20"]["comparaison"] == pytest.approx(18_385, abs=1) and D["ecarts_types20"]["hedonique"] == pytest.approx(11_462, abs=1)88 assert D["ecarts_types20"]["cout"] == pytest.approx(23_695, abs=1) and D["sd_conclusion"] == pytest.approx(10_450, abs=1)89 assert tuple(D["bande90"]) == (590_000, 625_000) and D["verite"]["V_true"] == pytest.approx(641_159, abs=1)90 assert D["residu_moyen_retenus"] == pytest.approx(-0.0853, abs=5e-5) and D["V_16"] == pytest.approx(604_689, abs=1)919293# ----------------------------------------------------------------------------- droits de mutation et financement (propres au ch. 45)94def test_mutation(D):95 assert D["base_mutation"] == 605_000 # 605 000 > 468 000 × 1,0296 attendu = 62_900 * 0.005 + (315_000 - 62_900) * 0.01 + (500_000 - 315_000) * 0.015 + (605_000 - 500_000) * 0.0397 assert D["droits_mutation"] == pytest.approx(attendu) and D["droits_mutation"] == pytest.approx(8_760.50, abs=0.01)98 assert D["droits_mutation_general"] == pytest.approx(7_185.50, abs=0.01)99100101def test_mise_de_fonds_et_prime():102 assert ch.mise_de_fonds_minimale(605_000) == pytest.approx(35_500) and ch.mise_de_fonds_minimale(500_000) == pytest.approx(25_000)103 assert ch.mise_de_fonds_minimale(1_000_000) == pytest.approx(75_000)104 with pytest.raises(ValueError):105 ch.mise_de_fonds_minimale(1_500_000)106 assert ch.taux_prime(0.80) == 0 and ch.taux_prime(0.8001) == 0.024 and ch.taux_prime(0.90) == 0.031 and ch.taux_prime(0.9413) == 0.04107 with pytest.raises(ValueError):108 ch.taux_prime(0.96)109110111def test_financement_minimal(D):112 f = D["financement"]["minimale"]; h = f["hypotheque"]113 assert f["mise"] == 35_500 and f["pret_base"] == 569_500 and f["rpv"] == pytest.approx(569_500 / 605_000)114 assert f["taux_prime"] == 0.04 and f["prime"] == pytest.approx(22_780) and f["pret"] == pytest.approx(592_280)115 assert f["taux"] == 0.0412 and f["amortissement_mois"] == 300116 # paiement : taux mensuel équivalent à une composition semestrielle (Loi sur l'intérêt, art. 6)117 r = (1 + 0.0412 / 2) ** (1 / 6) - 1118 assert h.taux_periodique == pytest.approx(r) and r == pytest.approx(fin.taux_mensuel_canadien(0.0412))119 assert f["paiement"] == pytest.approx(592_280 * r / (1 - (1 + r) ** -300)) and f["paiement"] == pytest.approx(3_154.16, abs=0.01)120 assert f["service_dette"] == pytest.approx(12 * f["paiement"]) and f["constante"] == pytest.approx(f["service_dette"] / f["pret"])121 # solde après 60 paiements = valeur actualisée des 240 paiements restants ; intérêts + capital de l'an 1 = 12 paiements122 assert f["solde_terme"] == pytest.approx(f["paiement"] * (1 - (1 + r) ** -240) / r) and f["solde_terme"] == pytest.approx(516_675.31, abs=0.01)123 assert f["interets_an1"] + f["capital_an1"] == pytest.approx(12 * f["paiement"], abs=1e-6)124 assert f["mqr"] == pytest.approx(0.0612) and f["paiement_mqr"] == pytest.approx(3_831.80, abs=0.01)125 assert f["revenu_min_abd"] == pytest.approx((12 * f["paiement_mqr"] + 4_076.01 + 2_400) / 0.39, abs=0.05)126 assert f["revenu_min_atd"] == pytest.approx((12 * f["paiement_mqr"] + 4_076.01 + 2_400 + 6_000) / 0.44, abs=0.05)127 assert f["contrainte_revenu"] == "ABD" and f["liquidites"] == pytest.approx(35_500 + 8_760.50 + 2_050.20, abs=0.01)128129130def test_financement_variantes(D):131 F = D["financement"]132 assert F["10 %"]["prime"] == pytest.approx(16_879.50) and F["10 %"]["paiement"] == pytest.approx(2_989.60, abs=0.01)133 assert F["20 %"]["assure"] is False and F["20 %"]["prime"] == 0 and F["20 %"]["taux"] == 0.0458 and F["20 %"]["mqr"] == pytest.approx(0.0658)134 assert F["20 %"]["paiement"] == pytest.approx(2_700.38, abs=0.01) and F["20 %"]["contrainte_revenu"] == "ATD"135 assert F["minimale 30 ans"]["paiement"] == pytest.approx(2_856.76, abs=0.01)136 # le paiement décroît avec la mise de fonds et avec l'amortissement137 assert F["minimale"]["paiement"] > F["10 %"]["paiement"] > F["20 %"]["paiement"] and F["minimale 30 ans"]["paiement"] < F["minimale"]["paiement"]138139140def test_renouvellement(D):141 r = D["renouvellement"].set_index("hausse_pb")142 assert r.loc[0, "paiement"] == pytest.approx(D["financement"]["minimale"]["paiement"], abs=1e-6) # même taux → même paiement143 assert r.loc[200, "paiement"] == pytest.approx(3_714.57, abs=0.01) and r.loc[200, "hausse_paiement_pct"] == pytest.approx(0.1777, abs=5e-5)144 assert r.loc[300, "paiement"] == pytest.approx(4_010.94, abs=0.01)145146147def test_valeur_emprunt(D):148 v = D["valeur_emprunt"]149 assert v[575_000.0]["pret_max"] == pytest.approx(546_250) and v[575_000.0]["mise_requise"] == pytest.approx(58_750) and v[575_000.0]["mise_supplementaire"] == pytest.approx(23_250)150 assert v[640_000.0]["base"] == 605_000 and v[640_000.0]["mise_supplementaire"] == 0151152153# ----------------------------------------------------------------------------- exercices154def test_exercices(E, D):155 assert E["ex1"]["par_m2_hab"] == pytest.approx(3_666.67, abs=0.01) and E["ex1"]["par_pi2_hab"] == pytest.approx(340.64, abs=0.01)156 assert E["ex1"]["ecart_pct"] == pytest.approx(0.0979, abs=5e-5)157 assert E["ex2"][575_000.0]["droits"] == pytest.approx(7_860.50) and E["ex2"][640_000.0]["droits"] == pytest.approx(9_810.50)158 assert E["ex2"][640_000.0]["mise"] == pytest.approx(39_000) and E["ex2"][640_000.0]["liquidites"] == pytest.approx(50_974.10, abs=0.01)159 assert E["ex6"][240_000.0]["V_cout"] == pytest.approx(542_500.97, abs=0.01) and E["ex6"][240_000.0]["arrondie"] == 600_000160 assert E["ex6"][285_000.0]["arrondie"] == 605_000 and E["ex6"][240_000.0]["fourchette"] == (540_000, 640_000)161 assert E["ex7"]["esperance_p80"] == pytest.approx(603_200) and E["ex7"]["paiement_nonH"] == pytest.approx(3_109.30, abs=0.01)162 assert E["ex7"]["pret_nonH"] == pytest.approx((596_000 - 34_600) * 1.04)163