SPB Git
9.3 KB · 132 lines python
1"""Tests du chapitre 46 — Cas 2 : triplex à Québec (dossier complet, données SYNTHÉTIQUES).2Identités (RNE, grille d'ajustements, hypothèque), cohérence avec traite.cas2 et ch32_plex (mêmes comparables, mêmes indications3de référence), valeurs citées dans le texte (tolérances : montants ±1 $ sauf mention, taux ±1e-4), exercices, présence des figures."""4import pathlib, sys5import numpy as np, pytest67ROOT = pathlib.Path(__file__).resolve().parents[3]8sys.path.insert(0, str(ROOT / "analysis/python/cases"))9import ch46_dossier as ch  # noqa: E40210import ch32_plex as C  # noqa: E40211from traite import cas2, finance as fin  # noqa: E402121314@pytest.fixture(scope="module")15def D():16    return ch.dossier()171819# ----------------------------------------------------------------------------- cohérence avec le module partagé et le ch. 3220def test_coherence_cas2_et_ch32(D):21    r = cas2.tout(); b = D["base"]22    assert b["rbp_contrat"] == pytest.approx(41832.0) and b["rbp_marche"] == pytest.approx(49332.0) and b["rbe"] == pytest.approx(40577.04)23    assert b["rne_declare"] == pytest.approx(r["base"]["rne"], abs=1e-6)             # RNE « déclaré » = RNE du ch. 32 / traite.cas224    assert b["rne_declare"] == pytest.approx(22033.9584, abs=0.01)25    assert list(D["comparables5"]["id_vente"]) == ["Q0187", "Q0222", "Q0243", "Q0207", "Q0235"]   # mêmes cinq comparables que le ch. 3226    assert D["revenu"]["v_mrb_comp"] == pytest.approx(922395.6, abs=0.01)27    assert D["revenu"]["taux_ajuste"] == pytest.approx(0.0302, abs=1e-4)28    assert D["revenu"]["v_cap_ajuste_rne_declare"] == pytest.approx(729549.19, abs=0.01)   # indication du ch. 32 retrouvée29    assert D["ch32"]["valeur_arrondie"] == 870000303132# ----------------------------------------------------------------------------- 46.5 taxes 2026 et RNE normalisé33def test_taxes_2026_et_rne(D):34    b = D["base"]; t = b["taxes"]35    assert t["fonciere"] == pytest.approx(495000 * 0.007464) and t["tarifs"] == pytest.approx(3 * (386 + 195))36    assert t["municipale"] == pytest.approx(5437.68, abs=0.01) and t["scolaire"] == pytest.approx((495000 - 25000) * 0.0007899, abs=0.01)37    assert b["ecart_taxes"] == pytest.approx(6370 - 5437.68 - 371.253, abs=0.01)38    assert b["rne"] == pytest.approx(b["rne_declare"] + b["ecart_taxes"], abs=1e-6)39    assert b["rne"] == pytest.approx(22595.03, abs=0.01)40    assert b["rne"] == pytest.approx(b["rbe"] - b["depenses_hors_reserve"] - b["reserve"])41    det = b["detail"]; assert det.loc[det["type"] == "imputée", "normalise"].iloc[0] == pytest.approx(0.04 * b["rbe"])42    assert b["ratio_depenses"] == pytest.approx(0.443, abs=0.001)43    assert b["rne_marche"] == pytest.approx(29579.03, abs=0.01)444546# ----------------------------------------------------------------------------- 46.6 régression auxiliaire et grille47def test_regression_et_grille(D):48    reg = D["regression"]; g = D["grille"]; t = g["tableau"]49    assert reg["n"] == 127 and 0.70 < reg["r2"] < 0.7550    assert reg["taux_annuel_marche"] == pytest.approx(np.exp(-12 * reg["beta"]["mois"]) - 1)51    assert abs(reg["t"]["loyer_relatif"]) < ch.T_MIN and (t["loyer_relatif"] == 0).all()          # coefficient non significatif → pas d'ajustement52    assert (t["stationnement"] == 0).all()                                                           # tous les comparables ont une case, comme le sujet53    for _, r in t.iterrows():                                                                         # identité : prix ajusté = prix + Σ ajustements ; brut = Σ|A|/P54        aj = [r[k] for k in ("temps", "superficie", "age", "etat", "stationnement", "loyer_marche", "loyer_relatif")]55        assert r["prix_ajuste"] == pytest.approx(r["prix_logement"] + sum(aj))56        assert r["brut_pct"] == pytest.approx(sum(abs(a) for a in aj) / r["prix_logement"])57    assert sum(g["poids"]) == pytest.approx(1.0)58    assert g["valeur"] == pytest.approx(3 * sum(w * p for w, p in zip(g["poids"], t["prix_ajuste"])))59    assert g["valeur"] == pytest.approx(1020366.74, abs=0.01) and g["valeur_par_logement"] == pytest.approx(340122.25, abs=0.01)60    q = t[t["id_vente"] == "Q0243"].iloc[0]61    assert q["prix_ajuste"] == pytest.approx(383849.14, abs=0.01) and q["brut_pct"] == pytest.approx(0.126, abs=0.001)626364# ----------------------------------------------------------------------------- 46.7 revenu65def test_revenu(D):66    rev = D["revenu"]; b = D["base"]67    assert rev["v_cap_ajuste"] == pytest.approx(b["rne"] / rev["taux_ajuste"]) and rev["v_cap_ajuste"] == pytest.approx(748126.24, abs=0.01)68    assert rev["v_cap_median"] == pytest.approx(b["rne"] / 0.0317) and rev["v_cap_comp"] == pytest.approx(b["rne"] / 0.0297)69    assert rev["taux_implicite_comparaison"] == pytest.approx(b["rne"] / D["grille"]["valeur"])70    assert rev["mrb_comp_ajuste_rho"] == pytest.approx(24.38, abs=0.01)717273# ----------------------------------------------------------------------------- 46.8 coût (contrôle)74def test_cout(D):75    co = D["cout"]76    assert co["cu_taille"] == pytest.approx(2409 * (262 / 165) ** -0.10) and co["cu"] == pytest.approx(co["cu_taille"] * 118.5 / 116.5)77    assert co["cout_neuf"] == pytest.approx(co["cu"] * 262) and co["age_effectif"] == pytest.approx(0.5 * 78 + 0.5 * 0.5 * 90)78    assert co["valeur"] == pytest.approx(co["terrain"] + co["cout_neuf"] * (1 - co["age_effectif"] / 90))79    assert co["valeur"] == pytest.approx(473111.44, abs=0.01)80    assert D["depreciation_extraite"]["depreciation"] < 0           # le marché implique une dépréciation négative : hypothèses de coût rejetées818283# ----------------------------------------------------------------------------- 46.10 réconciliation84def test_reconciliation(D):85    rec = D["reconciliation"]; ret = rec["retenues"]86    assert sum(rec["poids"].values()) == pytest.approx(1.0)87    assert rec["valeur_ponderee"] == pytest.approx(0.40 * ret["par_logement_ajuste"] + 0.35 * ret["mrb"] + 0.25 * ret["capitalisation"])88    assert rec["valeur_ponderee"] == pytest.approx(918016.72, abs=0.01) and rec["valeur_finale"] == 92000089    assert rec["fourchette_conclue"] == (870000.0, 970000.0)90    assert rec["taux_implicite"] == pytest.approx(D["base"]["rne"] / 920000) and rec["mrb_implicite"] == pytest.approx(920000 / 41832)91    assert rec["ratio_role"] == pytest.approx(920000 / 495000)92    assert rec["ecart_ch32"] == pytest.approx(50000)939495# ----------------------------------------------------------------------------- 46.9 financement96def test_financement(D):97    f = D["financement"]; h = fin.Hypotheque(f["pret"], 0.0412, 300)98    assert f["mise_de_fonds"] == pytest.approx(0.05 * 500000 + 0.10 * (920000 - 500000)) and f["mise_de_fonds"] == pytest.approx(C.mise_de_fonds_minimale_assuree(920000))99    assert f["pret"] == pytest.approx(920000 - f["mise_de_fonds"])100    assert h.taux_periodique == pytest.approx((1 + 0.0412 / 2) ** (1 / 6) - 1) and f["paiement_mensuel"] == pytest.approx(h.paiement)101    assert h.solde(300) == pytest.approx(0.0, abs=1e-6)102    assert f["interets_an1"] + f["capital_an1"] == pytest.approx(f["service_dette"], abs=1e-6)103    assert f["taux_admissible"] == pytest.approx(0.0612) and f["paiement_mensuel_tam"] == pytest.approx(fin.Hypotheque(f["pret"], 0.0612, 300).paiement)104    o = f["occupation"]105    assert o["cout_net_annuel"] == pytest.approx(f["service_dette"] + o["depenses_hors_gestion"] + o["cout_opportunite"] - o["loyers_percus_nets"])106    assert o["loyers_percus_nets"] == pytest.approx((1188 + 1251) * 12 * 0.97)107    assert o["cout_net_mensuel_hors_capital"] == pytest.approx(o["cout_net_mensuel"] - f["capital_rembourse_5ans"] / 60)108    m = f["mutation"]; assert m["base"] == 920000109    assert m["droits"] == pytest.approx(0.005 * 62900 + 0.01 * (315000 - 62900) + 0.015 * 185000 + 0.025 * 250000 + 0.03 * (920000 - 750000))110    assert 0.5 < f["part_paiement_couverte_brute"] < 0.6 and f["ratio_couverture_investisseur"] < 0.5111112113def test_reprise(D):114    r = D["reprise"]115    assert r["delta1"] == pytest.approx((1430 - 1047) * 12 * 0.97)116    assert r["va_rotation"] == pytest.approx(r["delta1"] / (0.06 - r["g_ecart"] + 0.13 * (1 + r["g_ecart"])))117    assert r["va_10ans_sans_rotation"] == pytest.approx(0.97 * 40791.4685, abs=0.5)       # ex. 32.7 du ch. 32 (sans la provision d'inoccupation)118    assert r["valeur_faculte_reprise"] == pytest.approx(r["va_10ans_sans_rotation"] - r["va_rotation"])119120121def test_exercices_et_figures(D):122    E = ch.exercices(D)123    assert E["1"]["v_cap_2026"] - E["1"]["v_cap_declare"] == pytest.approx(E["1"]["ecart"] / D["revenu"]["taux_ajuste"])124    assert E["5"]["cout_net_2e"] > E["5"]["cout_net_rc"]125    alt = [E["6"][k] for k in ("retenue 40/35/25", "45/30/25", "égale", "comparaison seule 60/40/0", "revenu dominant 20/20/60")]126    assert min(alt) < 920000 < max(alt) and E["6"]["valeur_inverse_variance"] == pytest.approx(968084.32, abs=0.01)127    assert E["5"]["promesse"]["paiement"] == pytest.approx(fin.Hypotheque(830500, 0.0412, 300).paiement) and E["5"]["promesse"]["droits_mutation"] == pytest.approx(16210.5)128    assert E["2"]["va_rotation_9_4"] == pytest.approx(39668.94, abs=0.01) and E["2"]["va_rotation_20"] == pytest.approx(19968.98, abs=0.01)129    assert E["7"]["terrain_m2_pour_egaler"] == pytest.approx((920000 - D["cout"]["contribution_batiment"]) / 310)130    for slug in ("loyers", "cascade", "mrb", "occupation", "fourchettes"):131        assert (ROOT / f"assets/figures/ch46/ch46-fig-{slug}.pdf").exists()132
Files · 81b1896e4502a619cf4f142fb8809253a8ff0bf3
Menu

Theme