SPB Git
9.4 KB · 160 lines python
1"""Chapitre 2 — Droits immobiliers et bases de valeur : identités (additivité, Gordon, annuité) et valeurs citées dans le texte,2les tableaux et les solutions (tolérances déclarées). Toutes les grandeurs du cas 3 proviennent de traite.cas3 (module partagé)."""3import sys, pathlib, math4import numpy as np, pytest5sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases"))6import ch02_droits as c27from traite import cas3, finance as fin8910@pytest.fixture(scope="module")11def res():12    return c2.main(verbose=False)131415# ----------------------------------------------------------------------------- identités1617def test_additivite_droit_temporaire_et_reversion():18    """V_pleine = V_droit temporaire + V_réversion lorsque la réversion est la capitalisation du revenu suivant (éq. 2.6, Gordon)."""19    for n in (1, 5, 15, 40):20        u = c2.usufruit_nue_propriete(30000.0, 0.01, 0.06, n)21        assert u["somme"] == pytest.approx(u["V_pleine"], rel=1e-12)22        assert 0 < u["part_usufruit"] < 123    # croissance nulle : usufruit = annuité, réversion = V/(1+r)^n24    d = c2.valeur_droit_temporaire_et_reversion(30000.0, 15, 0.06, 500000.0)25    assert d["droit_temporaire"] == pytest.approx(30000 * fin.facteur_va_annuite(0.06, 15))26    assert d["total"] == pytest.approx(500000.0)  # 30 000/0,06 = 500 000 : la somme redonne la perpétuité272829def test_formule_fermee_rotation():30    """Σ Δ[(1−ρ)(1+g)]^{t−1}/(1+r)^t = Δ/(r − g + ρ(1+g)) ; sans rotation = Δ/(r−g) ; cohérence somme finie / infinie."""31    D, r, g = 19697.0, 0.0725, 0.03532    for rho in (0.0, 0.05, 0.15, 0.5):33        brute = sum(D * ((1 - rho) * (1 + g)) ** (t - 1) / (1 + r) ** t for t in range(1, 5000))34        assert c2.va_ecart_rotation(D, r, g, rho) == pytest.approx(brute, rel=1e-9)35    assert c2.va_ecart_rotation(D, r, g, 0.0) == pytest.approx(D / (r - g))36    assert c2.va_ecart_rotation(D, r, g, 0.15, T=10) < c2.va_ecart_rotation(D, r, g, 0.15)37    with pytest.raises(ValueError):38        c2.va_ecart_rotation(D, 0.02, 0.035, 0.0)394041def test_valeur_bail_locataire_annuite():42    """éq. 2.4 : annuité ; croissante en n ; tend vers Δ/r."""43    v5 = c2.valeur_bail_locataire(3828.0, 5, 0.0725); v10 = c2.valeur_bail_locataire(3828.0, 10, 0.0725)44    assert v5 == pytest.approx(sum(3828 / 1.0725 ** t for t in range(1, 6)), rel=1e-12)45    assert v5 < v10 < 3828 / 0.072546    assert c2.valeur_bail_locataire(3828.0, 10000, 0.0725) == pytest.approx(3828 / 0.0725, rel=1e-6)474849# ----------------------------------------------------------------------------- valeurs citées (cas 3)5051def test_ecart_contractuel(res):52    r = res["revenus"]; e = res["ecarts"]53    assert r["rbp_contrat"] == pytest.approx(165540) and r["rbp_marche"] == pytest.approx(186036) and r["ecart_marche"] == pytest.approx(20496)54    assert res["ferme"]["ecart_net_annee1"] == pytest.approx(20496 * (1 - 0.039), abs=0.5)55    assert round(res["ferme"]["ecart_net_annee1"]) == 1969756    assert r["ecart_marche"] / r["rbp_marche"] == pytest.approx(0.110, abs=0.0005)57    l103 = e.loc[e["logement"] == 103].iloc[0]58    assert l103["ecart_mensuel"] == 319 and l103["ecart_annuel"] == 3828 and l103["anciennete"] == 1859    assert l103["ratio_contrat_marche"] == pytest.approx(0.695, abs=0.0005)60    assert e["ratio_contrat_marche"].mean() == pytest.approx(0.884, abs=0.0005)61    assert e["ratio_contrat_marche"].max() == pytest.approx(0.980, abs=0.0005)62    assert e["ecart_annuel"].sum() == 20496 and len(e) == 1263    # tableau 2.4, colonne écart annuel64    assert list(e["ecart_annuel"].astype(int)) == [1176, 2592, 3828, 264, 2412, 588, 2460, 2244, 336, 2640, 1152, 804]656667def test_decomposition_dcf(res):68    b = res["base"]69    # cohérence avec le module partagé (valeurs des ch. 29/30/47)70    t = cas3.tout()71    assert b["V_locateur"] == pytest.approx(t["dcf"]["valeur"], rel=1e-12)72    assert b["V_locateur"] == pytest.approx(2208566, abs=1)73    assert b["V_pleine"] == pytest.approx(2235712, abs=1)74    assert b["V_locataires"] == pytest.approx(27146, abs=1)75    assert b["V_locataires"] == pytest.approx(b["V_pleine"] - b["V_locateur"], rel=1e-12)76    assert b["part_locataires"] == pytest.approx(0.0121, abs=0.00005)77    assert b["rne1_contrat"] == pytest.approx(89956, abs=1) and b["rne1_marche"] == pytest.approx(108865, abs=1)78    assert b["rbe_contrat"] == pytest.approx(168444, abs=1) and b["rbe_marche"] == pytest.approx(188141, abs=1)79    assert b["va_flux_contrat"] == pytest.approx(662410, abs=1) and b["va_flux_marche"] == pytest.approx(699966, abs=1)80    assert b["va_vt_contrat"] == pytest.approx(1546156, abs=1) and b["va_vt_marche"] == pytest.approx(1535746, abs=1)81    pc, pm = b["proj_contrat"], b["proj_marche"]82    ecart_rne = (pm["rne"] - pc["rne"]).round(0).astype(int).tolist()83    assert ecart_rne[:6] == [18909, 13166, 8821, 3692, 1241, -394]84    assert ecart_rne[10] == -107585    ecart_rbp = (pm["rbp"] - pc["rbp"]).round(0).astype(int).tolist()86    assert ecart_rbp[:3] == [20496, 14545, 10048]87    assert abs(b["va_vt_marche"] / b["va_vt_contrat"] - 1) < 0.01   # valeurs terminales à moins de 1 %888990def test_capitalisation_naive_et_formules(res):91    c = res["cap"]; f = res["ferme"]92    assert c["Vcap_contrat"] == pytest.approx(2037503, abs=1) and c["Vcap_marche"] == pytest.approx(2465788, abs=1)93    assert c["ecart_capitalise"] == pytest.approx(428285, abs=1)94    assert c["ecart_capitalise"] / res["base"]["V_locataires"] > 1595    assert f["perpetuite_sans_rotation"] == pytest.approx(525244, abs=1)96    assert f["perpetuite_rotation"] == pytest.approx(102188, abs=1)97    assert 0.0375 + 0.15 * 1.035 == pytest.approx(0.19275)98    assert f["horizon10_rotation"] == pytest.approx(88094, abs=1)99100101def test_sensibilite(res):102    s = res["sensibilite"]103    assert len(s) == 15104    def v(rot, r): return float(s[(np.isclose(s["rotation"], rot)) & (np.isclose(s["taux"], r))]["V_locataires"].iloc[0])105    assert v(0.0, 0.0725) == pytest.approx(397186, abs=1)106    assert v(1 / 12, 0.0725) == pytest.approx(59858, abs=1)107    assert v(2 / 12, 0.0725) == pytest.approx(27146, abs=1)108    assert v(3 / 12, 0.0725) == pytest.approx(17360, abs=1)109    assert v(6 / 12, 0.0725) == pytest.approx(5195, abs=1)110    assert v(0.0, 0.0725) / v(6 / 12, 0.0725) == pytest.approx(76, abs=1)111    assert v(2 / 12, 0.0625) == pytest.approx(26673, abs=1) and v(2 / 12, 0.0825) == pytest.approx(27527, abs=1)112    part = s.set_index(["rotation", "taux"])["part"]113    assert part.max() == pytest.approx(0.1850, abs=0.0001) and part.min() == pytest.approx(0.0017, abs=0.0001)114    # monotonie : l'écart décroît avec la rotation à taux donné115    for r in c2.TAUX:116        col = s[np.isclose(s["taux"], r)].sort_values("rotation")["V_locataires"].tolist()117        assert all(a > b for a, b in zip(col, col[1:]))118    # V_locateur très sensible au taux (≈ 15 %), écart peu sensible119    assert float(s[(np.isclose(s["rotation"], 2 / 12)) & np.isclose(s["taux"], 0.0625)]["V_locateur"].iloc[0]) == pytest.approx(2396754, abs=1)120    assert float(s[(np.isclose(s["rotation"], 2 / 12)) & np.isclose(s["taux"], 0.0825)]["V_locateur"].iloc[0]) == pytest.approx(2037842, abs=1)121122123# ----------------------------------------------------------------------------- exercices et encadré124125class TestExercices:126    def test_ex3_bail_locataire(self, res):127        e = res["exercices"]["ex3"]128        assert e["ecart_annuel"] == 3828 and e["va_5ans"] == pytest.approx(15591, abs=1) and e["va_10ans"] == pytest.approx(26578, abs=1)129        assert e["va_perpetuite"] == pytest.approx(52800, abs=1)130        assert fin.facteur_va_annuite(0.0725, 5) == pytest.approx(4.0728, abs=0.0001) and fin.facteur_va_annuite(0.0725, 10) == pytest.approx(6.9431, abs=0.0001)131132    def test_ex4_usufruit(self, res):133        e = res["exercices"]["ex4"]134        assert e["V_pleine"] == pytest.approx(600000) and e["usufruit"] == pytest.approx(309341, abs=1) and e["nue_propriete"] == pytest.approx(290659, abs=1)135        assert e["V_n"] == pytest.approx(696581, abs=1) and e["part_usufruit"] == pytest.approx(0.516, abs=0.001)136        assert (1.01 / 1.06) ** 15 == pytest.approx(0.48443, abs=1e-5) and 1.06 ** -15 == pytest.approx(0.41727, abs=1e-5)137        v8 = c2.usufruit_nue_propriete(30000.0, 0.01, 0.06, 8)138        assert v8["usufruit"] == pytest.approx(192361, abs=1) and v8["nue_propriete"] == pytest.approx(407639, abs=1)139        assert v8["part_usufruit"] == pytest.approx(0.321, abs=0.001) and (1.01 / 1.06) ** 8 == pytest.approx(0.67940, abs=1e-5)140141    def test_ex5_date(self, res):142        e = res["exercices"]["ex5"]143        assert e["indice"] == pytest.approx(1.0985, abs=0.0001) and e["valeur_indexee"] == pytest.approx(514084, abs=1)144145    def test_ex6_formule_fermee(self):146        D, r, g = 19697.0, 0.0725, 0.035147        assert c2.va_ecart_rotation(D, r, g, 1 / 12) == pytest.approx(159168, abs=1)148        assert c2.va_ecart_rotation(D, r, g, 2 / 12) == pytest.approx(93795, abs=1)149        assert 0.0375 + (1 / 12) * 1.035 == pytest.approx(0.12375, abs=1e-5)150        assert 1 - (10 / 12) ** 2 == pytest.approx(0.306, abs=0.001)151152    def test_ex7_et_encadre(self, res):153        e = res["exercices"]["ex7"]154        assert e["V_sans_hyp"] == pytest.approx(1955638, abs=1) and e["incidence"] == pytest.approx(252928, abs=1)155        assert e["incidence"] / e["V_hyp"] == pytest.approx(0.1145, abs=0.0005)156        # ex. 2.7 : CAPEX 2027 de 85 000 $ actualisé un an157        assert 85000 / 1.0725 == pytest.approx(79254, abs=1)158        assert (2208566 + 85000 / 1.0725) == pytest.approx(2287820, abs=1)159        assert (85000 / 1.0725) / 2208566 == pytest.approx(0.036, abs=0.0005)160
Files · ae9e02e7cd803a16ec66687feb5b5de2a15d673f
Menu

Theme