SPB Git
10.1 KB · 163 lines python
1"""Tests du chapitre 8 — zonage et urbanisme (DONNÉES SYNTHÉTIQUES, graine 20261031).2Tolérances : 0,5 $ sur les montants cités arrondis au dollar ; 0,05 point de pourcentage sur les pourcentages cités avec deux3décimales ; 1e-9 sur les identités algébriques. Les valeurs « citées » sont celles de manuscript/chapters/ch08.tex et de4solutions/ch08.tex."""5import sys, pathlib, math6import numpy as np, pytest7sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases"))8import ch08_zonage as cas9from traite import comparables as cp1011TOL = 0.5121314@pytest.fixture(scope="module")15def res():16    return cas.main(ecrire=False)171819class TestDonneesSynthetiques:20    def test_structure(self, res):21        lots, ventes = res["lots"], res["ventes"]22        assert len(lots) == 400 and lots["zone_initiale"].value_counts().to_dict() == {"H1": 200, "C": 60, "H2": 60, "H3": 60, "P": 20}23        assert int(lots["rezone"].sum()) == 36 and set(lots.loc[lots["rezone"], "zone_apres_T0"]) == {"H2"}24        assert len(ventes) == 482 and int((ventes["zone_initiale"] == "H1").sum()) == 305 and int(ventes["traite_post"].sum()) == 2925        assert (ventes["prix_vente"] % 500 == 0).all()2627    def test_reproductibilite(self):28        l1, v1 = cas.charger_ou_generer(ecrire=False); l2, v2 = cas.charger_ou_generer(ecrire=False)29        assert v1.equals(v2) and l1.equals(l2)3031    def test_prime_injectee(self):32        assert cas.prime_rezonage(20) == pytest.approx(0.03) and cas.prime_rezonage(60) == pytest.approx(0.09) and cas.prime_rezonage(10) == pytest.approx(0.03)333435class TestPaires:36    def test_resume_cite(self, res):37        rp = res["resume_paires"]38        assert rp["n"] == 2939        assert rp["moyenne"] == pytest.approx(32285, abs=TOL) and rp["mediane"] == pytest.approx(19944, abs=TOL)40        assert rp["ecart_type"] == pytest.approx(51335, abs=TOL) and rp["erreur_type_moyenne"] == pytest.approx(9533, abs=TOL)41        assert rp["moyenne_pct"] * 100 == pytest.approx(9.29, abs=0.05) and rp["mediane_pct"] * 100 == pytest.approx(6.28, abs=0.05)42        assert rp["ic95_pct"][0] * 100 == pytest.approx(4.32, abs=0.05) and rp["ic95_pct"][1] * 100 == pytest.approx(14.26, abs=0.05)43        assert rp["n_jeunes"] == 16 and rp["n_vieux"] == 1344        assert rp["moyenne_pct_jeunes"] * 100 == pytest.approx(4.85, abs=0.05) and rp["moyenne_pct_vieux"] * 100 == pytest.approx(14.75, abs=0.05)45        assert res["prime_theorique_moyenne"] * 100 == pytest.approx(6.15, abs=0.05)4647    def test_identites_paires(self, res):48        p = res["paires"]49        assert (p["difference"] - (p["prix_traite"] - p["prix_temoin_ajuste"])).abs().max() < 1e-650        assert (p["prix_temoin_ajuste"] - (p["prix_temoin"] + p["ajust_temps"]) * p["facteur_physique"]).abs().max() < 1e-651        assert p["id_temoin"].is_unique and p["id_traite"].is_unique52        r = cp.ventes_appariees(p["prix_traite"].tolist(), p["prix_temoin_ajuste"].tolist())53        assert r["moyenne"] == pytest.approx(p["difference"].mean(), rel=1e-12)54        assert r["ecart_type"] / math.sqrt(r["n"]) == pytest.approx(res["resume_paires"]["erreur_type_moyenne"], rel=1e-12)5556    def test_ajustement_temps_coherent(self, res):57        p = res["paires"].iloc[0]58        from datetime import date59        mois = (date.fromisoformat(p["date_traite"]) - date.fromisoformat(p["date_temoin"])).days / 30.437560        assert p["ajust_temps"] == pytest.approx(cp.ajustement_temps_taux(p["prix_temoin"], cas.TAUX_MARCHE, mois), rel=1e-12)616263class TestDiD:64    def test_valeurs_citees(self, res):65        d = res["did"]66        assert d["n_obs"] == 305 and d["effectifs"] == {(False, False): 134, (False, True): 112, (True, False): 30, (True, True): 29}67        assert d["delta_2x2"] == pytest.approx(0.1061, abs=5e-5) and d["delta_2x2_pct"] * 100 == pytest.approx(11.19, abs=0.05)68        assert d["delta_reg"] == pytest.approx(0.0657, abs=5e-5) and d["se_delta"] == pytest.approx(0.0208, abs=5e-5)69        assert d["delta_reg_pct"] * 100 == pytest.approx(6.79, abs=0.05) and d["sigma_resid"] == pytest.approx(0.0709, abs=5e-5)70        assert d["coef"]["ln_hab"] == pytest.approx(0.4765, abs=5e-5) and d["coef"]["tendance"] == pytest.approx(0.0308, abs=5e-5)7172    def test_identite_2x2(self, res):73        d = res["did"]; m = d["moyennes_lnP"]74        assert d["delta_2x2"] == pytest.approx((m[(True, True)] - m[(True, False)]) - (m[(False, True)] - m[(False, False)]), rel=1e-12)75        # la régression retrouve, à l'erreur-type près, la prime moyenne injectée76        assert abs(d["delta_reg"] - math.log(1 + res["prime_theorique_moyenne"])) < 2 * d["se_delta"]777879class TestOutils:80    def test_grille(self, res):81        g = res["grille"]82        assert g["lot_conforme"] and g["logements_par_densite"] == 4 and g["logements_admissibles"] == 4 and g["aire_plancher_max_m2"] == pytest.approx(360.0)83        g2 = cas.densite_admissible(430.0, 150.0, 4, 0.6, 450.0)84        assert not g2["lot_conforme"] and g2["logements_admissibles"] == 28586    def test_redeveloppement(self, res):87        rd = res["redev"]88        assert rd["gdv"] == pytest.approx(2088000) and rd["couts_directs"] == pytest.approx(1044000) and rd["couts_indirects"] == pytest.approx(125280)89        assert rd["marge"] == pytest.approx(313200) and rd["financement"] == pytest.approx(62640) and rd["reste_avant_parcs"] == pytest.approx(512880)90        assert rd["valeur_residuelle"] == pytest.approx(466254.55, abs=0.01) and rd["contribution_parcs"] == pytest.approx(46625.45, abs=0.01)91        assert rd["valeur_residuelle"] + rd["contribution_parcs"] == pytest.approx(rd["reste_avant_parcs"], rel=1e-12)92        assert res["prime_option"] == pytest.approx(86254.55, abs=0.01) and res["prime_option_pct"] * 100 == pytest.approx(22.7, abs=0.05)9394    def test_grille_attendue(self, res):95        G = res["grille_attendue"]96        assert G.shape == (4, 4)97        assert G[2, 2] == pytest.approx(55462, abs=TOL) and G[3, 0] == pytest.approx(82998, abs=TOL) and G[0, 3] == pytest.approx(17118, abs=TOL)98        assert G[3, 0] == pytest.approx(res["prime_option"] * 1.08 ** -0.5, rel=1e-9)99        assert res["exemple_p75_T2"] == pytest.approx(427462, abs=TOL)100        assert res["exemple_p75_T2"] == pytest.approx(380000 + G[2, 2] - 8000, abs=TOL)101102    def test_droits_acquis(self, res):103        assert res["noi_duplex"] == pytest.approx(22620) and res["noi_maison"] == pytest.approx(19320) and res["delta_noi"] == pytest.approx(3300)104        attendu = {0.0: 60000.0, 0.01: 50769.23, 0.02: 44000.0, 0.05: 31428.57}105        for lam, v in attendu.items():106            assert res["v_delta"][lam] == pytest.approx(v, abs=0.01)107        assert res["v_delta_T"][0.0] == pytest.approx(44830, abs=TOL) and res["v_delta_T"][0.02] == pytest.approx(37252, abs=TOL)108        assert res["v_droits_acquis_lam02"] == pytest.approx(474000)109        # horizon infini = limite de l'horizon fini110        assert cas.valeur_surplus_horizon(3300, 0.055, 0.02, 1e6) == pytest.approx(cas.valeur_surplus_derogatoire(3300, 0.055, 0.02), rel=1e-9)111112    def test_parcs_et_agricole(self, res):113        assert res["parcs"]["terrain_max_m2"] == pytest.approx(240) and res["parcs"]["somme_max"] == pytest.approx(96000)114        assert res["v_agricole"] == pytest.approx(75000) and res["v_si_autorise"] == pytest.approx(157500) and res["v_attendue_agricole"] == pytest.approx(90796, abs=TOL)115116117class TestExercices:118    """Chiffres des solutions (solutions/ch08.tex)."""119120    def test_ex1_grille(self):121        g = cas.densite_admissible(540.0, 150.0, 4, 0.60, 450.0)122        assert g["lot_conforme"] and g["logements_admissibles"] == 3 and g["aire_plancher_max_m2"] == pytest.approx(324.0)123124    def test_ex2_parcs(self):125        c = cas.contribution_parcs(1250000.0, 3100.0)126        assert c["terrain_max_m2"] == pytest.approx(310) and c["somme_max"] == pytest.approx(125000)127128    def test_ex3_droits_acquis(self):129        d_noi = 3 * 12 * 1250 * (1 - 0.38) - 12 * 2600 * (1 - 0.30)130        assert d_noi == pytest.approx(6060.0)131        assert cas.valeur_surplus_derogatoire(d_noi, 0.06, 0.0) == pytest.approx(101000, abs=TOL)132        assert cas.valeur_surplus_derogatoire(d_noi, 0.06, 0.03) == pytest.approx(67333.33, abs=0.01)133        assert cas.valeur_surplus_horizon(d_noi, 0.06, 0.03, 20) == pytest.approx(56203, abs=TOL)134135    def test_ex4_did(self):136        m = {(False, False): 12.70, (False, True): 12.76, (True, False): 12.72, (True, True): 12.83}137        delta = (m[(True, True)] - m[(True, False)]) - (m[(False, True)] - m[(False, False)])138        assert delta == pytest.approx(0.05, abs=1e-9) and (math.exp(delta) - 1) * 100 == pytest.approx(5.13, abs=0.005)139140    def test_ex5_agricole(self):141        v_agr = 8 * 14000.0; v_aut = 110000.0 + 7.5 * 14000.0142        assert v_agr == pytest.approx(112000) and v_aut == pytest.approx(215000)143        assert cas.valeur_attendue_zone_agricole(v_agr, v_aut, 0.25, 2.0, 0.06, 6000.0) == pytest.approx(125837, abs=TOL)144        assert cas.valeur_attendue_zone_agricole(v_agr, v_aut, 0.50, 2.0, 0.06, 6000.0) == pytest.approx(145675, abs=TOL)145146    def test_ex6_option(self):147        rd = cas.valeur_residuelle_redeveloppement(3, 95.0, 5600.0, 2950.0, 0.12, 28000.0, 0.15, 0.03, 0.10, 0.0)148        assert rd["gdv"] == pytest.approx(1596000) and rd["valeur_residuelle"] == pytest.approx(308254.55, abs=0.01)149        rd2 = cas.valeur_residuelle_redeveloppement(3, 95.0, 5600.0, 2950.0, 0.12, 28000.0, 0.15, 0.03, 0.10, 45000.0)150        assert rd2["valeur_residuelle"] == pytest.approx(267345.45, abs=0.01)151        assert rd["valeur_residuelle"] - rd2["valeur_residuelle"] == pytest.approx(45000 / 1.10, rel=1e-9)152        assert cas.prime_option(350000.0, rd["valeur_residuelle"]) == 0.0153        assert cas.prime_option(280000.0, rd["valeur_residuelle"]) == pytest.approx(28254.55, abs=0.01)154155    def test_ex7_derivation(self):156        # V_Δ(T) → ΔRNE/(R+λ) ; et sensibilité : dV/dλ = −V/(R+λ)157        dn, R, lam = 3300.0, 0.055, 0.02158        v = cas.valeur_surplus_derogatoire(dn, R, lam)159        h = 1e-6160        deriv = (cas.valeur_surplus_derogatoire(dn, R, lam + h) - cas.valeur_surplus_derogatoire(dn, R, lam - h)) / (2 * h)161        assert deriv == pytest.approx(-v / (R + lam), rel=1e-6)162        assert cas.valeur_surplus_derogatoire(dn, R, lam + 0.002) == pytest.approx(42857.14, abs=0.01)163
Files · ae9e02e7cd803a16ec66687feb5b5de2a15d673f
Menu

Theme