"""Tests du chapitre 6 — démembrements et modalités du droit de propriété (DONNÉES SYNTHÉTIQUES, hypothèses déclarées). Tolérances : 0,5 $ sur les montants arrondis cités dans le texte ; 1e-6 (relatif) sur les identités ; 0,05 point sur les pourcentages cités ; valeurs exactes pour les droits de mutation (calcul en dollars et cents).""" import sys, pathlib, math import numpy as np, pytest sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases")) import ch06_demembrements as cas TOL = 0.5 V = cas.V_ARRONDI @pytest.fixture(scope="module") def noi(): return cas.rne_cas3() class TestMortalite: def test_survie_monotone_et_bornes(self): for x in (55, 65, 85): p = [cas.survie(x, t) for t in range(0, cas.OMEGA - x + 1)] assert p[0] == 1.0 and p[-1] == 0.0 assert all(a >= b for a, b in zip(p, p[1:])) def test_esperances_citees(self): tm = cas.table_mortalite() assert tm.loc[65, "esperance_vie"] == pytest.approx(17.9, abs=0.05) assert tm.loc[55, "esperance_vie"] == pytest.approx(25.9, abs=0.05) assert tm.loc[75, "esperance_vie"] == pytest.approx(11.1, abs=0.05) assert tm.loc[85, "esperance_vie"] == pytest.approx(6.2, abs=0.05) assert tm.loc[65, "p10"] == pytest.approx(0.789, abs=5e-4) and tm.loc[65, "p20"] == pytest.approx(0.424, abs=5e-4) assert all(tm["esperance_vie"].diff().dropna() < 0) class TestUsufruit: def test_valeur_cas1_et_loyer(self): c = cas.valeur_cas1() assert c["prediction_hedonique"] == pytest.approx(637952.85, abs=0.01) assert round(c["prediction_hedonique"], -3) == V assert cas.loyer_net(V) == pytest.approx(22330.0, abs=1e-6) assert cas.loyer_net(V) / 12 == pytest.approx(1861, abs=0.5) @pytest.mark.parametrize("n,U,NP,part", [(10, 183443, 454557, 71.2), (20, 314141, 323859, 50.8), (30, 407260, 230740, 36.2)]) def test_terme_fixe(self, n, U, NP, part): u = cas.usufruit_terme(V, n) assert u["U"] == pytest.approx(U, abs=TOL) and u["NP"] == pytest.approx(NP, abs=TOL) assert u["NP"] == pytest.approx(u["NP_formule"], rel=1e-9) # NP = V((1+g)/(1+r))^n assert u["U"] + u["NP"] == pytest.approx(V, rel=1e-12) assert u["part_NP"] * 100 == pytest.approx(part, abs=0.05) @pytest.mark.parametrize("x,U,NP,part,ann,rente", [(55, 359836, 278164, 43.6, 16.114, 26300), (65, 280357, 357643, 56.1, 12.555, 25335), (75, 197560, 440440, 69.0, 8.847, 24409), (85, 123863, 514137, 80.6, 5.547, 23599)]) def test_viager(self, x, U, NP, part, ann, rente): u = cas.usufruit_viager(V, x) assert u["U"] == pytest.approx(U, abs=TOL) and u["NP"] == pytest.approx(NP, abs=TOL) assert u["NP_direct"] == pytest.approx(u["NP"], rel=1e-9) # proposition : identité du partage viager assert u["part_NP"] * 100 == pytest.approx(part, abs=0.05) assert u["annuite_viagere"] == pytest.approx(ann, abs=5e-4) assert cas.rente_viagere_equivalente(V, x) == pytest.approx(rente, abs=TOL) def test_identite_tous_ages_et_taux(self): for x in range(50, 100, 7): for r in (0.03, 0.05, 0.08): u = cas.usufruit_viager(V, x, r, 0.015) assert u["U"] + u["NP_direct"] == pytest.approx(V, rel=1e-9) def test_approximation_terme_fixe_sous_estime_np(self): u = cas.usufruit_viager(V, 65) a = cas.approximation_terme_espere(V, 65) assert a["n_eff"] == pytest.approx(18.36, abs=0.005) and a["n_eff"] == pytest.approx(u["esperance_vie"] + 0.5, abs=1e-9) assert a["part_NP"] * 100 == pytest.approx(53.7, abs=0.05) assert u["NP"] - a["NP"] == pytest.approx(15271, abs=TOL) for x in range(50, 100, 5): # Jensen : NP viager ≥ NP du terme espéré for r in (0.03, 0.05, 0.08): assert cas.usufruit_viager(V, x, r)["NP"] >= cas.approximation_terme_espere(V, x, r)["NP"] def test_grille_sensibilite(self): g = cas.grille_usufruit(V) attendu = {55: (54.4, 43.6, 35.4), 60: (59.8, 49.7, 41.6), 65: (65.4, 56.1, 48.4), 70: (71.0, 62.6, 55.6), 75: (76.3, 69.0, 62.8), 80: (81.2, 75.1, 69.7), 85: (85.5, 80.6, 76.1)} for age, vals in attendu.items(): assert tuple(round(v * 100, 1) for v in g.loc[age]) == vals assert (0.654 - 0.484) * V == pytest.approx(108000, abs=600) assert all(g.diff().dropna().values.ravel() > 0) # NP/V croît avec l'âge assert all(g.diff(axis=1).dropna(axis=1).values.ravel() < 0) # et décroît avec r class TestEmphyteose: def test_reference(self, noi): assert noi == pytest.approx(89955.75, abs=0.01) e = cas.emphyteose(noi, cas.RENTE_EMPH, cas.N_EMPH) assert e["V_pleine"] == pytest.approx(1999017, abs=TOL) assert e["V_emph"] == pytest.approx(1166242, abs=TOL) and e["part_emph"] * 100 == pytest.approx(58.3, abs=0.05) assert e["V_rentes"] == pytest.approx(388430, abs=TOL) and e["V_reprise"] == pytest.approx(444344, abs=TOL) assert e["V_prop"] == pytest.approx(832774, abs=TOL) assert e["somme"] == pytest.approx(e["V_pleine"], rel=1e-9) def test_identite_toutes_durees(self, noi): for n in range(1, 101): e = cas.emphyteose(noi, cas.RENTE_EMPH, n) assert e["somme"] == pytest.approx(e["V_pleine"], rel=1e-9) g = cas.grille_emphyteose(noi, cas.RENTE_EMPH) assert all(g["V_emph"].diff().dropna() < 0) # décroît quand la durée restante baisse def test_grille_citee(self, noi): g = cas.grille_emphyteose(noi, cas.RENTE_EMPH) attendu = {60: (1426059, 421175, 151782, 572958, 71.3), 50: (1351745, 414022, 233250, 647272, 67.6), 40: (1240623, 399951, 358443, 758394, 62.1), 20: (834713, 317820, 846483, 1164303, 41.8), 10: (487489, 210707, 1300820, 1511528, 24.4), 5: (263447, 123006, 1612564, 1735570, 13.2)} for n, (ve, vr, vp, vt, part) in attendu.items(): assert g.loc[n, "V_emph"] == pytest.approx(ve, abs=TOL) and g.loc[n, "V_rentes"] == pytest.approx(vr, abs=TOL) assert g.loc[n, "V_reprise"] == pytest.approx(vp, abs=TOL) and g.loc[n, "V_prop"] == pytest.approx(vt, abs=TOL) assert g.loc[n, "part_emph"] * 100 == pytest.approx(part, abs=0.05) def test_taux_distincts(self, noi): e = cas.emphyteose(noi, cas.RENTE_EMPH, cas.N_EMPH, r_prop=0.055) assert e["V_prop"] == pytest.approx(1190025, abs=TOL) assert e["somme"] == pytest.approx(2356268, abs=TOL) assert e["somme"] - e["V_pleine"] == pytest.approx(357251, abs=TOL) class TestAutres: def test_indivision(self): assert cas.part_indivise(V, 0.5, 0.0)["valeur_part"] == pytest.approx(319000, abs=TOL) assert cas.part_indivise(V, 0.5, 0.10)["valeur_part"] == pytest.approx(287100, abs=TOL) assert cas.part_indivise(V, 0.5, 0.20)["valeur_part"] == pytest.approx(255200, abs=TOL) def test_servitude(self): c = cas.valeur_cas1() assert c["elasticite_terrain"] == pytest.approx(0.0937, abs=5e-4) s = cas.servitude_terrain(V, c["terrain_m2"], 120, c["elasticite_terrain"]) assert s["delta"] == pytest.approx(12725, abs=TOL) and s["delta_pct"] * 100 == pytest.approx(2.0, abs=0.05) def test_rang(self): r = cas.rang_hypothecaire(560000, [455000, 90000, 40000]) assert [x["recouvre"] for x in r] == [455000, 90000, 15000, 0] assert r[2]["taux_recouvrement"] == pytest.approx(0.375) assert sum(x["recouvre"] for x in r) == 560000 def test_mutation(self): m = cas.droit_mutation(V) assert [round(t["montant"], 2) for t in m["detail"]] == [314.50, 2521.00, 4845.00] assert m["total"] == pytest.approx(7680.50, abs=0.005) assert m["taux_effectif"] * 100 == pytest.approx(1.204, abs=0.0005) m2 = cas.droit_mutation(750000, taux_municipal=[(500000, 0.02)]) assert m2["droit_base"] == pytest.approx(9360.50, abs=0.005) and m2["supplement_municipal"] == pytest.approx(1250.0, abs=0.005) assert cas.droit_mutation(62900)["total"] == pytest.approx(314.50, abs=0.005) assert cas.droit_mutation(0)["total"] == 0.0 def test_vice_et_registre(self): q = cas.quantum_vice(520000, 46000, 9000, 6500, False) assert q["reduction_prix"] == 37000 and q["total"] == 37000 and q["part_prix"] * 100 == pytest.approx(7.1, abs=0.05) assert cas.quantum_vice(520000, 46000, 9000, 6500, True)["total"] == 43500 assert cas.budget_registre(5)["cout"] == 15.0 class TestExercices: """Chiffres des solutions (solutions/ch06.tex).""" def test_ex1(self): assert 612000 - 6500 - 4000 == 601500 def test_ex3(self): p = cas.part_indivise(900000, 1 / 3, 0.12) assert p["valeur_proportionnelle"] == pytest.approx(300000, abs=1e-6) and p["valeur_part"] == pytest.approx(264000, abs=1e-6) def test_ex4(self): paires = [(548000, 531000), (602000, 586500), (575000, 559000)] d = [a - b for a, b in paires]; rel = [(a - b) / a for a, b in paires] assert d == [17000, 15500, 16000] and np.mean(d) == pytest.approx(16166.67, abs=0.01) assert [round(x * 100, 2) for x in rel] == [3.10, 2.57, 2.78] assert np.mean(rel) * 100 == pytest.approx(2.82, abs=0.005) assert np.mean(rel) * 638000 == pytest.approx(17991, abs=TOL) def test_ex5(self): assert cas.quantum_vice(520000, 46000, 9000, 6500, False)["prix_ajuste"] == 483000 def test_ex6(self): Vx, r, g, x = 455000, 0.045, 0.01, 72 assert cas.loyer_net(Vx, r, g) == pytest.approx(15925.0, abs=1e-6) u = cas.usufruit_viager(Vx, x, r, g) assert u["U"] == pytest.approx(158934, abs=TOL) and u["NP"] == pytest.approx(296066, abs=TOL) assert u["part_NP"] * 100 == pytest.approx(65.1, abs=0.05) and u["esperance_vie"] == pytest.approx(13.0, abs=0.05) a = cas.approximation_terme_espere(Vx, x, r, g) assert a["n_eff"] == pytest.approx(13.49, abs=0.005) assert a["U"] == pytest.approx(167650, abs=TOL) and a["NP"] == pytest.approx(287350, abs=TOL) assert a["part_NP"] * 100 == pytest.approx(63.2, abs=0.05) and u["NP"] - a["NP"] == pytest.approx(8716, abs=TOL) t = cas.usufruit_terme(Vx, 13, r, g) assert t["NP"] == pytest.approx(292198, abs=TOL) assert cas.rente_viagere_equivalente(Vx, x, r, g) == pytest.approx(17038, abs=TOL) t30 = cas.usufruit_terme(Vx, 30, r, g) assert t30["U"] == pytest.approx(291257, abs=TOL) and t30["NP"] == pytest.approx(163743, abs=TOL) def test_ex7(self): e = cas.emphyteose(142000, 24000, 28, 0.07, 0.02) assert e["V_pleine"] == pytest.approx(2840000, abs=1e-6) assert e["V_emph"] == pytest.approx(1805044, abs=TOL) and e["V_rentes"] == pytest.approx(291291, abs=TOL) assert e["V_reprise"] == pytest.approx(743665, abs=TOL) and e["V_prop"] == pytest.approx(1034956, abs=TOL) assert e["somme"] == pytest.approx(2840000, rel=1e-9) e2 = cas.emphyteose(142000, 24000, 68, 0.07, 0.02) assert e2["V_emph"] == pytest.approx(2390930, abs=TOL) and e2["V_prop"] == pytest.approx(449070, abs=TOL) m = cas.droit_mutation(e["V_emph"]) assert m["total"] == pytest.approx(314.50 + 2521.00 + 0.015 * (e["V_emph"] - 315000), abs=0.01) assert m["total"] == pytest.approx(25186.16, abs=0.01) e3 = cas.emphyteose(142000, 24000, 28, 0.07, 0.02, r_prop=0.055) assert e3["V_prop"] == pytest.approx(1443131, abs=TOL) and e3["somme"] == pytest.approx(3248175, abs=TOL)