SPB Git
11.8 KB · 179 lines python
1"""Tests du chapitre 26 — baux et loyers (cas 3 et cas 2, données synthétiques).2Identités (formule fermée vs série, survie, durée espérée, loyer économique sur la valeur par capitalisation = loyer3contractuel, cohérence avec le chapitre 2 : VA agrégée 102 188 $ à ρ = 15 %), valeurs citées dans ch26.tex et4solutions/ch26.tex (tolérances déclarées), calendrier des avis, tarif D, présence des figures."""5import sys, pathlib, datetime as dt6sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1]))7sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases"))8import pytest9import numpy as np10from traite import cas3, cas2, finance as fin11from ch26_etat_locatif import (calculer, exercices, va_ecart, va_ecart_serie, va_annuite, survie, esperance_duree, cout_electricite_mensuel,12                               normalisation_electricite, calendrier, croissance_max_1955, loyer_net_equivalent, TARIF_D, PROFIL_KWH, JOURS)1314ROOT = pathlib.Path(__file__).resolve().parents[3]151617@pytest.fixture(scope="module")18def res():19    return calculer()202122@pytest.fixture(scope="module")23def ex(res):24    return exercices(res)252627# ----------------------------------------------------------------------------- identités28def test_formule_fermee_egale_serie():29    for d, r, g, rho in ((3679.0, 0.0725, 0.035, 0.094), (1000.0, 0.06, 0.02, 0.05), (500.0, 0.09, 0.0, 0.3)):30        assert va_ecart(d, r, g, rho) == pytest.approx(va_ecart_serie(d, r, g, rho, T=2000), rel=1e-9)31    # sans rotation : capitalisation perpétuelle à r − g ; rotation totale : un seul écart actualisé32    assert va_ecart(1000.0, 0.0725, 0.035, 0.0) == pytest.approx(1000.0 / 0.0375)33    assert va_ecart(1000.0, 0.0725, 0.035, 1.0) == pytest.approx(1000.0 / 1.0725)34    with pytest.raises(ValueError):35        va_ecart(1000.0, 0.03, 0.035, 0.0)363738def test_survie_et_duree():39    assert survie(0.15, 0) == 1.0 and survie(0.15, 1) == pytest.approx(0.85)40    # E[n] = Σ t ρ (1−ρ)^(t−1) = 1/ρ41    rho = 0.094; t = np.arange(1, 5000)42    assert np.sum(t * rho * (1 - rho) ** (t - 1)) == pytest.approx(esperance_duree(rho), rel=1e-9)43    assert esperance_duree(0.15) == pytest.approx(6.6667, abs=1e-4)44    with pytest.raises(ValueError):45        esperance_duree(0.0)464748def test_coherence_chapitre_2(res):49    """La somme des VA par logement (ρ = 15 %) redonne la VA agrégée du chapitre 2 (102 188 $) et l'écart net 19 697 $."""50    va = res["va"]51    assert va["delta1_net"].sum() == pytest.approx(20496 * (1 - 0.039), abs=0.01)52    assert va["va_rho_0.150"].sum() == pytest.approx(res["va_agregee_cas"], rel=1e-12)53    assert res["va_agregee_cas"] == pytest.approx(102188, abs=1)54    assert res["sens"]["sans_rotation"] == pytest.approx(525244, abs=1)55    assert va["va_perpetuel"].sum() == pytest.approx(res["sens"]["sans_rotation"], rel=1e-12)565758def test_loyer_economique_identite(res):59    """Sur la valeur par capitalisation directe (RNE/R), le loyer économique est exactement le loyer contractuel."""60    e = res["econ_cap"]61    assert e["rbp_requis"] == pytest.approx(res["base"]["rbp_contrat"], abs=1e-6)62    assert e["loyer_economique_mensuel"] == pytest.approx(e["loyer_contrat_mensuel"], abs=1e-9)63    # reconstruction : RNE* = R·V ; RBE* (1 − m) − DE_hg − RR = RNE*64    er = res["econ_role"]65    assert er["rne_requis"] == pytest.approx(res["R"] * 2_200_000, rel=1e-12)66    assert er["rbe_requis"] * 0.96 - er["de_hors_gestion"] - er["reserve"] == pytest.approx(er["rne_requis"], abs=1e-6)67    assert er["rbp_requis"] * (1 - 0.039) + res["base"]["autres_revenus"] == pytest.approx(er["rbe_requis"], abs=1e-6)686970def test_tarif_d_structure():71    c = cout_electricite_mensuel(1900, 31)72    assert c["tranche1_kwh"] == 1240 and c["tranche2_kwh"] == 66073    assert c["total"] == pytest.approx(31 * 0.46154 + 1240 * 0.07065 + 660 * 0.11142, abs=1e-9)74    c0 = cout_electricite_mensuel(0, 30)75    assert c0["total"] == pytest.approx(30 * 0.46154)76    n = normalisation_electricite()77    assert n["tableau"]["total"].sum() == pytest.approx(n["cout_annuel"]) and n["kwh_annuel"] == 1200078    assert n["tableau"]["tranche1_kwh"].sum() + n["tableau"]["tranche2_kwh"].sum() == pytest.approx(12000)79    assert n["loyer_nu"] == pytest.approx(n["loyer_chauffe"] - n["cout_mensuel_moyen"])80    assert sum(JOURS) == 365 and sum(PROFIL_KWH) == 12000818283def test_calendrier():84    c = calendrier(dt.date(2027, 6, 30), 12)85    assert c["avis_modification_debut"] == dt.date(2026, 12, 30) and c["avis_modification_fin"] == dt.date(2027, 3, 30)86    assert c["avis_reprise_eviction_fin"] == dt.date(2026, 12, 30) and c["debut_bail_suivant"] == dt.date(2027, 7, 1)87    assert c["nouveau_locataire_fixation_10j"] == dt.date(2027, 7, 11) and c["nouveau_locataire_sans_avis_2mois"] == dt.date(2027, 9, 1)88    c6 = calendrier(dt.date(2026, 12, 31), 6)89    assert c6["avis_modification_debut"] == dt.date(2026, 10, 31) and c6["avis_modification_fin"] == dt.date(2026, 11, 30)90    assert c6["avis_reprise_eviction_fin"] == dt.date(2026, 11, 30)919293# ----------------------------------------------------------------------------- valeurs citées dans le texte94def test_valeurs_citees_etat_locatif(res):95    ind = res["ind"]96    assert ind["rbp_contrat"] == 165540 and ind["rbp_marche"] == 186036 and ind["ecart_marche"] == 2049697    assert ind["ecart_pondere"] == pytest.approx(0.1102, abs=5e-5)98    assert ind["ratio_pondere"] == pytest.approx(0.890, abs=5e-4)99    assert ind["ratio_moyen_simple"] == pytest.approx(0.884, abs=5e-4)100    assert ind["ecart_mensuel_moyen"] == pytest.approx(142.33, abs=0.01)101    assert ind["anciennete_moyenne"] == pytest.approx(6.33, abs=0.01) and ind["anciennete_mediane"] == 6.5 and ind["anciennete_max"] == 18102    assert ind["nb_sous_marche_5"] == 8 and ind["nb_sous_marche_10"] == 6 and ind["nb_sous_marche_20"] == 2103    assert ind["part_ecart_top3"] == pytest.approx(0.442, abs=5e-4)104    assert ind["loyer_moyen_contrat"] == pytest.approx(1149.58, abs=0.01) and ind["loyer_moyen_marche"] == pytest.approx(1291.92, abs=0.01)105    assert ind["loyer_m2_contrat_moyen"] == pytest.approx(16.72, abs=0.01) and ind["loyer_m2_marche_moyen"] == pytest.approx(18.79, abs=0.01)106    t = ind["tableau"].set_index("logement")107    assert t.loc[103, "ecart_mensuel"] == 319 and t.loc[103, "anciennete"] == 18 and t.loc[103, "ratio"] == pytest.approx(0.695, abs=5e-4)108    assert (t["mois_echeance"] == 9.9).all()109    # cohérence avec le module partagé (chapitres 27, 29, 30, 47)110    r = cas3.revenus(res["etat"], res["hyp"])111    assert r["rbe"] == pytest.approx(168443.94, abs=0.01)112113114def test_valeurs_citees_va_et_sensibilite(res):115    va = res["va"].set_index("logement")116    assert va.loc[103, "va_rho_0.094"] == pytest.approx(27292, abs=1) and va.loc[103, "va_rho_0.150"] == pytest.approx(19085, abs=1)117    assert va.loc[103, "va_rho_0.085"] == pytest.approx(29318, abs=1) and va.loc[103, "va_perpetuel"] == pytest.approx(98099, abs=1)118    assert va.loc[104, "va_rho_0.150"] == pytest.approx(1316, abs=1)119    assert va["va_rho_0.094"].sum() == pytest.approx(146128, abs=1) and va["va_rho_0.085"].sum() == pytest.approx(156977, abs=1)120    s = res["sens"]121    assert s["par_taux"].loc["rho=0.050", "r=0.0725"] == pytest.approx(220691, abs=1)122    assert s["par_taux"].loc["rho=0.250", "r=0.0725"] == pytest.approx(66487, abs=1)123    assert s["par_taux"].loc["rho=0.150", "r=0.0625"] == pytest.approx(107779, abs=1) and s["par_taux"].loc["rho=0.150", "r=0.0825"] == pytest.approx(97148, abs=1)124    assert s["par_croissance"].loc["rho=0.150", "g=0.025"] == pytest.approx(97872, abs=1) and s["par_croissance"].loc["rho=0.150", "g=0.045"] == pytest.approx(106902, abs=1)125    assert res["esperance_duree"][0.094] == pytest.approx(10.64, abs=0.01) and res["esperance_duree"][0.085] == pytest.approx(11.76, abs=0.01)126127128def test_valeurs_citees_economique_normalisation(res):129    e = res["econ_role"]130    assert e["rne_requis"] == pytest.approx(97130, abs=0.5) and e["rbe_requis"] == pytest.approx(175917, abs=1) and e["rbp_requis"] == pytest.approx(173316, abs=1)131    assert e["loyer_economique_mensuel"] == pytest.approx(1203.59, abs=0.01) and e["de_hors_gestion"] == pytest.approx(63647.43, abs=0.01)132    n = res["norm"]133    assert n["cout_annuel"] == pytest.approx(1073.34, abs=0.01) and n["cout_mensuel_moyen"] == pytest.approx(89.45, abs=0.01)134    assert n["kwh_tranche2"] == 1400 and n["mois_depassement"] == 3 and n["loyer_nu"] == pytest.approx(1360.55, abs=0.01)135    assert n["part_redevance"] == pytest.approx(0.157, abs=5e-4) and n["cout_mensuel_indexe_2027"] == pytest.approx(92.13, abs=0.01)136    tab = n["tableau"].set_index("mois")137    assert tab.loc["janv.", "total"] == pytest.approx(175.45, abs=0.01) and tab.loc["juil.", "total"] == pytest.approx(49.63, abs=0.01)138139140def test_valeurs_citees_cas2(res):141    c2 = res["cas2"]142    assert c2["rbp_contrat"] == 41832 and c2["rbp_marche"] == 49332 and c2["ecart_marche"] == 7500143    assert c2["ecart_pondere"] == pytest.approx(0.152, abs=5e-4) and c2["anciennete_moyenne"] == 4.0 and c2["nb_sous_marche_10"] == 2144    t = c2["tableau"].set_index("logement")145    assert t.loc["RC", "ecart_mensuel"] == 383 and t.loc["RC", "ratio"] == pytest.approx(0.732, abs=5e-4) and t.loc["3e", "ecart_mensuel"] == 0146    assert cas2.revenus(c2["data"]["loyers"])["rbe"] == pytest.approx(40577.04, abs=0.01)147148149# ----------------------------------------------------------------------------- exercices (solutions/ch26.tex)150def test_exercices(ex):151    e2 = ex["2"]152    assert e2["rbp_contrat"] == 85200 and e2["rbp_marche"] == 94800 and e2["ecart_pondere"] == pytest.approx(0.1013, abs=5e-5)153    assert e2["nb_sous_marche_10"] == 2 and e2["nb_sous_marche_5"] == 4 and e2["anciennete_moyenne"] == 6.5 and e2["part_ecart_top3"] == pytest.approx(0.706, abs=5e-4)154    e3 = ex["3"]155    assert e3["variante"]["cout_annuel"] == pytest.approx(1258.54, abs=0.01) and e3["variante"]["loyer_nu"] == pytest.approx(1395.12, abs=0.01) and e3["variante"]["mois_depassement"] == 5156    e4 = ex["4"]157    assert e4["delta1_net"] == pytest.approx(3678.71, abs=0.01) and e4["va_094"] == pytest.approx(27292, abs=1) and e4["va_150"] == pytest.approx(19085, abs=1)158    assert e4["annuite_094"] == pytest.approx(26643, abs=1) and e4["annuite_150"] == pytest.approx(18920, abs=1) and e4["perpetuel"] == pytest.approx(98099, abs=1)159    assert e4["annuite_094"] == pytest.approx(va_annuite(e4["delta1_net"], 0.0725, 1 / 0.094), rel=1e-12)160    e5 = ex["5"]161    assert e5["R"] == pytest.approx(0.0317, abs=5e-5) and e5["role"]["rbp_requis"] == pytest.approx(35021, abs=1) and e5["role"]["loyer_mensuel_moyen"] == pytest.approx(972.80, abs=0.01)162    assert e5["cap"]["rbp_requis"] == pytest.approx(e5["rbp_contrat"], abs=1e-6) and e5["V_cap"] == pytest.approx(695078, abs=1)163    e6 = ex["6"]164    assert e6["g_max"] == pytest.approx(croissance_max_1955(1600, 1850), rel=1e-12) and e6["g_max"] == pytest.approx(0.0295, abs=5e-5)165    assert e6["va_tal"] == pytest.approx(82846, abs=1) and e6["va_saut"] == pytest.approx(87621, abs=1) and e6["va_reg"] == pytest.approx(82609, abs=1)166    assert e6["loyer_an5_tal"] == pytest.approx(1807.82, abs=0.01) and e6["loyer_an6_tal"] == pytest.approx(1863.86, abs=0.01) and not e6["depasse_tal"]167    e7 = ex["7"]168    assert e7["loyer_net"] == 26500 and e7["net_m2"] == pytest.approx(240.91, abs=0.01) and e7["brut_m2"] == pytest.approx(327.27, abs=0.01)169    assert e7["va_net_5ans"] == pytest.approx(fin.van([0] + [26500] * 5, 0.0725), rel=1e-12) and e7["va_net_5ans"] == pytest.approx(107932, abs=1)170    assert e7["va_residentiel_5ans"] == pytest.approx(64770, abs=1) and e7["ratio_va"] == pytest.approx(1.666, abs=5e-4)171    assert loyer_net_equivalent(36000, 9500, 110)["loyer_net"] == 26500172    c1 = ex["1"]["bail6"]173    assert c1["nouveau_locataire_fixation_10j"] == dt.date(2027, 1, 11)174175176def test_figures_presentes():177    for slug in ("contrat-marche", "anciennete", "va-ecart", "normalisation"):178        assert (ROOT / "assets/figures/ch26" / f"ch26-fig-{slug}.pdf").exists()179
Files · b39d09a4fc988c2cb0fb8ba9c86d745a81de3752
Menu

Theme