SPB Git
14.5 KB · 212 lines python
1"""Tests du chapitre 24 — dépréciation et désuétudes du sujet du cas 1, extraction de marché, profils et exercices.2Tolérances : 0,01 $ sur les montants recalculés ; 0,5 $ sur les montants arrondis cités ; 5e-4 sur les taux cités en % à deux3décimales ; 1e-9 sur les identités."""4import sys, pathlib5import numpy as np, pytest6sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases"))7import ch24_depreciation as cas8from traite import cout as ct91011@pytest.fixture(scope="module")12def data():13    d = cas.charger()14    var = cas.variantes_physique(d["cn"], d["fiche"], d["travaux"])15    se = cas.carence_salle_eau(d["devis"], d["cn"])16    w, vl = cas.extraction(d["ventes"], d["modele"], d["cn"]["total"])17    aj = cas.ajuster_profil(w); vlr = cas.valeur_terrain_retenue(vl)18    pr = cas.profils(d["modele"], d["sujet"], vlr["retenue"], aj["quadratique"])19    vs = cas.valeur_sujet(d["cn"], vlr["retenue"], aj["quadratique"], se["desuetude"], var)20    return {**d, "var": var, "se": se, "w": w, "vl": vl, "aj": aj, "vlr": vlr, "pr": pr, "vs": vs, "ages": cas.ages_sujet(d["fiche"])}212223class TestAges:24    def test_ages_cites(self, data):25        a = data["ages"]26        assert a["chronologique"] == 28 and a["effectif_structure"] == pytest.approx(29.0, abs=1e-9)27        assert a["vie_restante"] == pytest.approx(31.0, abs=1e-9) and a["vie_economique"] == 6028        assert a["indice_etat"] == pytest.approx(2.809, abs=5e-4) and a["age_effectif_pondere"] == pytest.approx(32.8, abs=0.05)2930    def test_convention_age_effectif(self):31        # a_eff = (1 − λ) a + λ φ(c) L : structure 28 ans, état 3 (φ = 0,5), L = 60 → 0,5·28 + 0,5·30 = 2932        import ch11_inspection as c1133        assert c11.age_effectif(28, 60, 3, 0.5) == 29.0 and c11.age_effectif(28, 60, 1, 0.5) == 15.5 and c11.age_effectif(28, 60, 5, 0.5) == 44.034        assert ct.vie_restante(60, 29) == 31.0 and ct.vie_restante(60, 75) == 0.0353637class TestPhysique:38    def test_quatre_lectures(self, data):39        v = data["var"]; cn = data["cn"]["total"]40        assert cn == pytest.approx(397567.33, abs=0.01)41        assert v["age_vie_chrono"] == pytest.approx(185531.42, abs=0.01) and v["age_vie_chrono"] / cn == pytest.approx(0.4667, abs=5e-4)42        assert v["age_vie_effectif"] == pytest.approx(192157.55, abs=0.01)43        assert v["modifiee"]["curable"] == pytest.approx(10479.98, abs=0.01) and v["modifiee"]["incurable"] == pytest.approx(187092.22, abs=0.01)44        assert v["modifiee"]["total"] == pytest.approx(197572.20, abs=0.01) and v["modifiee"]["total"] / cn == pytest.approx(0.4970, abs=5e-4)45        ve = v["ventilee"]46        assert sum(ve["courte_duree"].values()) == pytest.approx(130584.55, abs=0.01) and ve["longue_duree"] == pytest.approx(80025.35, abs=0.01)47        assert ve["base_longue"] == pytest.approx(165569.69, abs=0.01) and ve["total"] == pytest.approx(221089.88, abs=0.01)48        assert ve["taux"] == pytest.approx(0.5561, abs=5e-4) and len(ve["courte_duree"]) == 184950    def test_identites_ventilee(self, data):51        v = data["var"]; ve = v["ventilee"]; f = data["fiche"]; cn = data["cn"]["total"]52        courtes = f[(~f["longue_duree"]) & (f["composante"] != "Entrée asphaltée")]53        assert courtes["cout_neuf"].sum() == pytest.approx(221517.66, abs=0.01)54        # base longue = C_N − curable − Σ coût neuf courte durée55        assert ve["base_longue"] == pytest.approx(cn - ve["curable"] - courtes["cout_neuf"].sum(), abs=1e-6)56        assert ve["total"] == pytest.approx(ve["curable"] + sum(ve["courte_duree"].values()) + ve["longue_duree"], abs=1e-9)57        # proposition : D_vent − D_simple = Cur (1 − r_S) + Σ C_k (r_k − r_S)58        rS = 29.0 / 60.059        ecart = ve["curable"] * (1 - rS) + sum(ve["courte_duree"].values()) - courtes["cout_neuf"].sum() * rS60        assert ve["total"] - v["age_vie_effectif"] == pytest.approx(ecart, abs=1e-6) and ecart == pytest.approx(28932.33, abs=0.01)61        # chaque composante : dépréciation = coût neuf × min(a_eff / L, 1)62        for r in courtes.itertuples():63            assert ve["courte_duree"][r.composante] == pytest.approx(r.cout_neuf * min(r.age_effectif / r.vie_utile, 1.0), abs=1e-6)64        assert ve["courte_duree"]["Ventilateur récupérateur de chaleur (VRC)"] == pytest.approx(7501.27, abs=0.01)65        assert ve["courte_duree"]["Revêtement extérieur (brique et fibrociment)"] == pytest.approx(18089.57, abs=0.01)666768class TestDesuetudes:69    def test_salle_eau(self, data):70        se = data["se"]71        assert se["cout_si_neuf_direct"] == pytest.approx(6309.76, abs=0.01) and se["cout_si_neuf"] == pytest.approx(8394.69, abs=0.01)72        assert se["desuetude"] == pytest.approx(7605.31, abs=0.01) and se["desuetude"] == pytest.approx(16000 - se["cout_si_neuf"], abs=1e-9)7374    def test_exemples(self):75        ex = cas.exemples_incurables()76        assert ex["mrb_annuel"] == pytest.approx(19.82, abs=1e-9) and ex["perte_loyer_capitalisee"] == pytest.approx(17838.00, abs=0.01)77        assert ex["perte_rne_capitalisee_totale"] == pytest.approx(133333.33, abs=0.01) and ex["perte_rne_batiment"] == pytest.approx(93333.33, abs=0.01)78        assert ex["paires"] == pytest.approx(18000.0, abs=1e-9)798081class TestExtraction:82    def test_valeur_terrain(self, data):83        vl, vlr = data["vl"], data["vlr"]84        assert int(vl.loc["Hull", "n"]) == 13 and vl.loc["Hull", "vl_mediane"] == pytest.approx(276953.99, abs=0.01)85        assert vl.loc["Hull", "part_mediane"] == pytest.approx(0.505, abs=5e-4) and vlr["retenue"] == 277000.086        assert vlr["retenue"] / 620 == pytest.approx(447, abs=0.5)87        assert vl.loc["Aylmer", "vl_mediane"] == pytest.approx(321579, abs=1) and vl.loc["Gatineau", "vl_mediane"] == pytest.approx(270089, abs=1)88        assert vl.loc["Buckingham", "vl_mediane"] == pytest.approx(182678, abs=1) and vl.loc["Masson-Angers", "vl_mediane"] == pytest.approx(175657, abs=1)89        assert int(vl["n"].sum()) == 339091    def test_identite_extraction(self, data):92        w = data["w"]93        assert len(w) == 60094        assert np.allclose(w["D"], w["cn"] - (w["p_adj"] - w["vl"]), atol=1e-6) and np.allclose(w["taux"], w["D"] / w["cn"], atol=1e-12)95        assert data["modele"]["croissance_mensuelle"] == pytest.approx(0.00364, abs=5e-6)9697    def test_tranches_et_ajustements(self, data):98        tr = cas.tranches(data["w"])99        assert list(tr["size"]) == [33, 47, 76, 115, 124, 101, 104]100        assert tr.loc["0-10", "median"] == pytest.approx(0.038, abs=5e-4) and tr.loc["21-30", "median"] == pytest.approx(0.192, abs=5e-4)101        assert tr.loc["61-100", "median"] == pytest.approx(0.403, abs=5e-4)102        aj = data["aj"]; q = aj["quadratique"]["beta"]; l = aj["lineaire"]["beta"]103        assert l[0] == pytest.approx(0.0878, abs=5e-5) and l[1] == pytest.approx(0.00447, abs=5e-6) and aj["lineaire"]["r2"] == pytest.approx(0.228, abs=5e-4)104        assert q[0] == pytest.approx(0.0107, abs=5e-5) and q[1] == pytest.approx(0.00891, abs=5e-6) and q[2] == pytest.approx(-0.0000514, abs=5e-8)105        assert aj["quadratique"]["r2"] == pytest.approx(0.250, abs=5e-4)106        assert aj["taux_annuel_origine"] == pytest.approx(0.00619, abs=5e-6) and aj["vie_implicite"] == pytest.approx(162, abs=0.5)107        assert float(cas.taux_quadratique(aj["quadratique"], 28)) == pytest.approx(0.2200, abs=5e-5)108        assert float(cas.taux_quadratique(aj["quadratique"], 60)) == pytest.approx(0.3605, abs=5e-5)109110111class TestProfils:112    def test_profils_cites(self, data):113        pr = data["pr"]114        assert pr["prix_neuf"] == pytest.approx(720727, abs=1) and pr["alpha"] == pytest.approx(0.384, abs=5e-4)115        assert pr["hedonique_total"][28] == pytest.approx(0.1148, abs=5e-5) and pr["generateur_total"][28] == pytest.approx(0.1223, abs=5e-5)116        assert pr["hedonique_bat"][28] == pytest.approx(0.1865, abs=5e-4) and pr["generateur_bat"][28] == pytest.approx(0.1986, abs=5e-4)117        assert pr["age_vie"][28] == pytest.approx(28 / 60, abs=1e-12) and pr["age_vie"][60] == 1.0118        # conversion : d_B (1 − α) = d_total119        assert np.allclose(pr["hedonique_bat"] * (1 - pr["alpha"]), pr["hedonique_total"])120        # profils croissants sur 0-80 ans121        assert np.all(np.diff(pr["extraction"]) > 0) and np.all(np.diff(pr["hedonique_bat"]) > 0)122123    def test_generateur(self):124        assert cas.profil_total_generateur(28) == pytest.approx(1 - np.exp(-0.0055 * 28 + 0.00003 * 784), abs=1e-12)125126127class TestValeur:128    def test_deux_lectures(self, data):129        vs = data["vs"]; A, B = vs["A_ventilee"], vs["B_extraction"]130        assert vs["d_extraite"] == pytest.approx(87461.05, abs=0.01)131        assert A["valeur"] == pytest.approx(445872.14, abs=0.01) and A["taux_depreciation"] == pytest.approx(0.5752, abs=5e-4)132        assert B["valeur"] == pytest.approx(579500.97, abs=0.01) and B["depreciation_totale"] == pytest.approx(95066.36, abs=0.01)133        assert B["taux_depreciation"] == pytest.approx(0.2391, abs=5e-4) and B["contribution_batiment"] == pytest.approx(302500.97, abs=0.01)134        assert vs["ecart"] == pytest.approx(133628.83, abs=0.01) and vs["valeur_arrondie"] == 580000.0135        # identité V = V_L + C_N − D_phys − D_fonc − D_ext136        assert B["valeur"] == pytest.approx(277000 + 397567.33 - 87461.05 - 7605.31, abs=0.02)137138    def test_ordre_externe(self, data):139        cn = data["cn"]["total"]; vs = data["vs"]140        # une désuétude externe de 10 % appliquée sur C_N plutôt que sur la base restante surestime la déduction141        avant = 0.10 * cn; apres = 0.10 * vs["B_extraction"]["contribution_batiment"]142        assert avant == pytest.approx(39756.73, abs=0.01) and apres == pytest.approx(30250.10, abs=0.01) and avant - apres == pytest.approx(9506.64, abs=0.01)143144145class TestSensibilite:146    def test_grille(self, data):147        g = cas.grille_age_vie(data["cn"], data["var"]["curable"])148        assert g.loc[60, 29] == pytest.approx(0.4970, abs=5e-4) and g.loc[50, 37] == pytest.approx(0.747, abs=5e-4) and g.loc[100, 20] == pytest.approx(0.221, abs=5e-4)149        assert g.loc[80, 29] == pytest.approx(0.379, abs=5e-4)150151    def test_extraction_variantes(self, data):152        sx = cas.sensibilite_extraction(data["ventes"], data["modele"], data["cn"]["total"]).set_index("variante")153        assert sx.loc["cas de base", "taux_28"] == pytest.approx(0.2200, abs=5e-5)154        assert sx.loc["ventes récentes ≤ 5 ans", "taux_28"] == pytest.approx(0.1968, abs=5e-5) and sx.loc["ventes récentes ≤ 15 ans", "taux_28"] == pytest.approx(0.2720, abs=5e-5)155        assert sx.loc["ventes récentes ≤ 15 ans", "vl_hull"] == pytest.approx(303775, abs=1)156        assert sx["taux_28"].min() == pytest.approx(0.1968, abs=5e-5) and sx["taux_28"].max() == pytest.approx(0.2720, abs=5e-5)157158    def test_tornade(self, data):159        sx = cas.sensibilite_extraction(data["ventes"], data["modele"], data["cn"]["total"])160        t = {o["param"]: o for o in cas.tornade(data["cn"], data["vlr"]["retenue"], data["vs"]["d_extraite"], data["se"]["desuetude"], data["var"], sx)}161        assert t["Valeur du terrain ±15 %"]["bas"] == pytest.approx(537951, abs=1) and t["Valeur du terrain ±15 %"]["haut"] == pytest.approx(621051, abs=1)162        assert t["Choix d'extraction (taux à 28 ans)"]["bas"] == pytest.approx(558807, abs=1) and t["Choix d'extraction (taux à 28 ans)"]["haut"] == pytest.approx(588716, abs=1)163        assert t["Coût neuf ±8,6 % (date des prix)"]["bas"] == pytest.approx(554944, abs=1) and t["Coût neuf ±8,6 % (date des prix)"]["haut"] == pytest.approx(606170, abs=1)164        assert t["Lecture ventilée (ch. 11) au lieu de l'extraction"]["bas"] == pytest.approx(445872, abs=1)165166167class TestExercices:168    def test_ex1(self):169        assert ct.depreciation_age_vie(320000, 15, 60) == 80000 and ct.vie_restante(60, 15) == 45170        m = ct.depreciation_age_vie_modifiee(320000, 9000, 15, 60)171        assert m["incurable"] == 77750 and m["total"] == 86750 and m["total"] / 320000 == pytest.approx(0.2711, abs=5e-5)172        assert ct.depreciation_age_vie_modifiee(320000, 9000, 20, 60)["total"] == pytest.approx(112666.67, abs=0.01)173174    def test_ex2(self):175        comps = [ct.Composante("toiture", 9000, 12, 20), ct.Composante("fenêtres", 18000, 10, 30), ct.Composante("chauffe-eau", 1500, 8, 12)]176        v = ct.depreciation_ventilee(320000, 9000, comps, 15, 60)177        assert v["courte_duree"] == {"toiture": 5400, "fenêtres": 6000, "chauffe-eau": 1000} and v["base_longue"] == 282500178        assert v["longue_duree"] == 70625 and v["total"] == 92025 and v["total"] / 320000 == pytest.approx(0.2876, abs=5e-5)179        assert v["total"] - 86750 == 5275180181    def test_ex3(self):182        assert ct.desuetude_fonctionnelle_curable(16000, 8394.69, 0) == pytest.approx(7605.31, abs=1e-9)183        assert 22000 * 0.6 + 3000 == 16200   # base de reproduction : solde non déprécié + coût d'enlèvement184185    def test_ex4(self):186        assert ct.desuetude_incurable_par_loyer(75, 19.82 * 12) == pytest.approx(17838, abs=1e-9)187        assert ct.desuetude_incurable_par_rne(6000, 0.045, 0.7) == pytest.approx(93333.33, abs=0.01)188        assert ct.desuetude_incurable_par_rne(6000, 0.055, 0.7) == pytest.approx(76363.64, abs=0.01)189190    def test_ex5(self):191        ventes = [(610000, 260000, 420000, 12), (545000, 250000, 390000, 25), (480000, 240000, 380000, 41), (520000, 255000, 410000, 33)]192        e = [ct.extraction_depreciation_marche(p, l, c, a) for p, l, c, a in ventes]193        assert [x["depreciation"] for x in e] == [70000, 95000, 140000, 145000]194        ta = [x["taux_annuel"] for x in e]195        assert ta[0] == pytest.approx(0.01389, abs=5e-6) and ta[2] == pytest.approx(0.00899, abs=5e-6)196        m = float(np.mean(ta))197        assert m == pytest.approx(0.01083, abs=5e-6) and 1 / m == pytest.approx(92.3, abs=0.05) and 30 * m * 400000 == pytest.approx(130006, abs=1)198        m2 = float(np.mean([ct.extraction_depreciation_marche(p, l + 20000, c, a)["taux_annuel"] for p, l, c, a in ventes]))199        assert m2 == pytest.approx(0.01303, abs=5e-6) and 30 * m2 * 400000 == pytest.approx(156350, abs=1)200201    def test_ex6(self):202        b1, b2, al = -0.005097, 0.00002642, 0.384203        t28 = 1 - np.exp(b1 * 28 + b2 * 784)204        assert t28 == pytest.approx(0.1149, abs=5e-5) and t28 / (1 - al) == pytest.approx(0.1865, abs=5e-5)205        assert (1 - np.exp(b1 * 50 + b2 * 2500)) / (1 - al) == pytest.approx(0.2793, abs=5e-5)206        assert -(b1 + 2 * b2 * 28) == pytest.approx(0.003617, abs=5e-7) and -b1 / (2 * b2) == pytest.approx(96.5, abs=0.05)207        assert 1 - t28 / (28 / 60) == pytest.approx(0.754, abs=5e-4)208209    def test_ex7(self):210        assert 6130.62 + 15989.85 == pytest.approx(22120.47, abs=0.01)211        assert 0.10 * 397567.33 - 0.10 * 302500.97 == pytest.approx(9506.64, abs=0.01)212
Files · 2a26d365cc23544c8343a62de5e56cfc4c06cb46
Menu

Theme