SPB Git
7.8 KB · 133 lines python
1"""Tests du chapitre 32 — Duplex à quintuplex : identités, cohérence avec traite.cas2 (partagé avec les ch. 26, 27, 46),2valeurs citées dans le texte (tolérances déclarées), règles de financement vérifiées, exercices, présence des figures."""3import pathlib, sys4import numpy as np, pytest56ROOT = pathlib.Path(__file__).resolve().parents[3]7sys.path.insert(0, str(ROOT / "analysis/python/cases"))8import ch32_plex as C  # noqa: E4029from traite import cas2, finance as fin  # noqa: E402101112@pytest.fixture(scope="module")13def R():14    return C.calculer()151617# ----------------------------------------------------------------------------- cohérence avec le module partagé18def test_coherence_cas2_partage(R):19    r = cas2.tout(); b = R["base"]20    assert b["rbp_contrat"] == pytest.approx(41832.0) and b["rbp_marche"] == pytest.approx(49332.0)21    assert b["rne"] == pytest.approx(22033.9584, abs=0.01) and b["ratio_depenses"] == pytest.approx(0.457, abs=0.001)22    assert R["indicateurs"] == r["indicateurs"]23    assert R["indications"]["v_mrb_127"] == pytest.approx(r["valeur_mrb"], abs=0.01)24    assert R["indications"]["v_cap_127"] == pytest.approx(r["valeur_capitalisation"], abs=0.01)25    assert R["indications"]["v_log_127"] == pytest.approx(r["valeur_par_logement"], abs=0.01)262728def test_valeurs_citees_indicateurs(R):29    ind = R["indicateurs"]30    assert ind["n"] == 127 and ind["n_triplex"] == 6731    assert ind["mrb_median_triplex"] == pytest.approx(19.82, abs=0.005) and ind["taux_median"] == pytest.approx(0.0317, abs=5e-5)32    assert ind["prix_par_logement_triplex"] == pytest.approx(281000, abs=1)33    assert R["indications"]["v_mrb_127"] == pytest.approx(829110.24, abs=1)34    assert R["indications"]["v_cap_127"] == pytest.approx(695077.55, abs=1)35    assert R["indications"]["v_log_127"] == pytest.approx(843000, abs=1)36    pt = R["par_taille"]; assert pt.loc[2, "n"] == 60 and pt.loc[3, "n"] == 6737    assert pt.loc[3, "prix_median"] == pytest.approx(843000, abs=1) and pt.loc[2, "prix_median"] == pytest.approx(583500, abs=1)383940# ----------------------------------------------------------------------------- identités41def test_identite_mrb_taux(R):42    """R = (1 − v)(1 − d)/MRB pour chaque vente du jeu (v = 2,4 %, d = ratio de la vente), aux arrondis du fichier près."""43    g = R["decomposition"]44    lhs = g["taux_global"]; rhs = (1 - 0.024) * (1 - g["ratio_depenses"]) / g["mrb"]45    assert np.max(np.abs(lhs - rhs)) < 2e-4464748def test_decomposition_rho(R):49    """R_c = ρ R_m lorsque les dépenses sont proportionnelles au revenu (jeu synthétique)."""50    g = R["decomposition"]51    assert np.max(np.abs(g["taux_contrat_reconstitue"] - g["taux_global"])) < 2e-452    assert g["taux_marche"].median() > g["taux_global"].median()53    assert R["regression"]["b_rho"] > 0 and 0.03 < R["regression"]["a"] < 0.04545556def test_valeurs_citees_regression_et_indications(R):57    reg = R["regression"]; I = R["indications"]58    assert reg["a"] == pytest.approx(0.0345, abs=1e-4) and reg["b_rho"] == pytest.approx(0.0284, abs=1e-4)59    assert I["rho_sujet"] == pytest.approx(0.848, abs=5e-4)60    assert I["taux_ajuste"] == pytest.approx(0.0302, abs=1e-4)61    assert I["v_cap_ajuste"] == pytest.approx(729549, abs=1)62    assert I["v_log_comp"] == pytest.approx(918000, abs=1) and I["v_mrb_comp"] == pytest.approx(922396, abs=1)63    assert I["v_cap_comp"] == pytest.approx(741884, abs=1)64    assert I["valeur_ponderee"] == pytest.approx(872426, abs=1) and I["valeur_arrondie"] == 87000065    assert I["valeur_ponderee"] == pytest.approx(sum(C.POIDS_INDICATIONS[k] * v for k, v in I["retenues"].items()))66    assert abs(sum(C.POIDS_INDICATIONS.values()) - 1) < 1e-1267    assert I["v_cap_ratio_ventes"] == pytest.approx(863880, abs=1) and I["v_log_127_indexe"] == pytest.approx(900926, abs=1)686970def test_comparables_selection(R):71    comp5 = R["comparables5"]72    assert len(comp5) == 5 and set(comp5["id_vente"]) == {"Q0187", "Q0222", "Q0243", "Q0207", "Q0235"}73    assert (comp5["quartier"] == "Limoilou").all() and (comp5["nb_logements"] == 3).all()74    assert comp5["distance"].is_monotonic_increasing and len(R["candidats"]) == 2875    assert comp5["prix_par_logement"].median() == pytest.approx(306000, abs=1) and comp5["mrb"].median() == pytest.approx(22.05, abs=0.005)767778def test_prime_occupant(R):79    p = R["prime"]; I = R["indications"]80    assert p["v_investisseur_contrat"] + p["v_rattrapage"] + p["prime"] == pytest.approx(I["valeur_ponderee"])81    assert p["v_investisseur_contrat"] == pytest.approx(440679.17, abs=0.01) and p["prime_pct"] == pytest.approx(0.335, abs=0.001)828384# ----------------------------------------------------------------------------- financement (règles vérifiées, notes-C §4.3, §4.2)85def test_mise_de_fonds_minimale():86    assert C.mise_de_fonds_minimale_assuree(450_000) == pytest.approx(22_500)87    assert C.mise_de_fonds_minimale_assuree(843_000) == pytest.approx(59_300)88    assert C.mise_de_fonds_minimale_assuree(870_000) == pytest.approx(62_000)89    assert C.mise_de_fonds_minimale_assuree(1_500_000) == pytest.approx(125_000)90    assert np.isnan(C.mise_de_fonds_minimale_assuree(1_600_000))919293def test_financement_occupant(R):94    f = R["financement"]95    assert f["pret"] == pytest.approx(808_000) and f["rpv"] == pytest.approx(0.9287, abs=1e-4)96    assert f["paiement_mensuel"] == pytest.approx(4302.97, abs=0.01) and f["service_dette"] == pytest.approx(51635.62, abs=0.01)97    # composition semestrielle : taux mensuel équivalent (1 + 0,0412/2)^(1/6) − 198    i_m = (1 + 0.0412 / 2) ** (1 / 6) - 199    assert f["paiement_mensuel"] == pytest.approx(808_000 * i_m / (1 - (1 + i_m) ** -300), abs=0.01)100    assert f["taux_admissible"] == pytest.approx(0.0612) and f["paiement_mensuel_tam"] == pytest.approx(5227.41, abs=0.01)101    h = fin.Hypotheque(f["pret"], f["taux"], f["amort_mois"]); assert h.solde(300) == pytest.approx(0.0, abs=1e-6)102    o = R["occupation"]103    assert o["cout_net_mensuel"] == pytest.approx(3532.11, abs=0.01) and o["cout_net_mensuel_hors_capital"] == pytest.approx(1813.08, abs=0.01)104    assert o["cout_net_annuel"] == pytest.approx(o["sortie_brute"] - o["loyers_percus_nets"])105106107def test_sensibilite(R):108    S = R["sensibilite"]; L = S["loyers"]109    assert L["v_cap_ajuste"].is_monotonic_increasing and L["v_mrb"].is_monotonic_increasing110    assert L.loc[L["rho"] == 1.0, "rne"].item() == pytest.approx(R["rne_marche"], abs=0.01)111    F = S["financement"]112    assert F["cout_net_mensuel"].iloc[4] > F["cout_net_mensuel"].iloc[0]       # +100 pb coûte plus cher113    assert F["service_dette"].iloc[1] < F["service_dette"].iloc[0]           # 30 ans < 25 ans114    T = S["taux"]; assert np.allclose(T["valeur"], R["base"]["rne"] / T["taux"])115116117# ----------------------------------------------------------------------------- exercices (solutions/ch32.tex)118def test_exercices():119    E = C.exercices()120    assert E["1"]["mrb"] == pytest.approx(18.75) and E["1"]["taux"] == pytest.approx(E["1"]["identite"])121    assert E["1"]["rne"] == pytest.approx(18763.68)122    assert E["2"]["843000.0"]["mise"] == pytest.approx(59300) and E["2"]["450000.0"]["mise"] == pytest.approx(22500)123    e3 = E["3"]; assert e3["R_m"] - e3["R_c"] == pytest.approx(e3["terme"]) and e3["ecart_pb"] == pytest.approx(105.36, abs=0.01)124    assert E["4"]["2e"]["cout_net_mensuel"] > E["4"]["RC"]["cout_net_mensuel"]125    assert E["6"]["ponderations"]["comparaison seule (50/50/0)"] == pytest.approx(920197.8, abs=0.1)126    assert E["6"]["ponderations"]["revenu dominant (20/20/60)"] == pytest.approx(805809, abs=1)127    assert E["7"]["va_1an"] == pytest.approx(4335.85, abs=0.01) and E["7"]["va_10ans"] == pytest.approx(40791.47, abs=0.01)128129130def test_figures_presentes():131    for slug in ("mrb-taille-quartier", "taux-loyer-relatif", "prix-logement-quartier", "plex-apciq-rmr", "sensibilite-occupant"):132        assert (ROOT / f"assets/figures/ch32/ch32-fig-{slug}.pdf").exists(), slug133
Files · ae9e02e7cd803a16ec66687feb5b5de2a15d673f
Menu

Theme