spb/traite-evaluation-qc
Public
1"""Tests du chapitre 44 — rénovation et efficacité énergétique (sujet du cas 1, DONNÉES SYNTHÉTIQUES).2Tolérances : 0,01 $ sur les montants recalculés ; 5e-4 sur les taux cités en % à deux décimales ; 1e-9 sur les identités."""3import sys, pathlib, math4import numpy as np, pytest5sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases"))6import ch44_renovation as cas7from traite import finance as fin, cas18910@pytest.fixture(scope="module")11def data():12 mm = cas.mesures()13 d = cas1.charger(); mod = cas1.hedonique(d["ventes"]); vs = float(cas1.predire(mod, cas1.sujet_df(d["sujet"]))[0])14 return {"mm": mm, "fA": cas.flux_mesure(mm["A"]), "fB": cas.flux_mesure(mm["B"]), "fAB": cas.flux_mesure(mm["AB"]),15 "fI": cas.flux_mesure(mm["A_incr"]), "ventes": d["ventes"], "modele": mod, "vs": vs}161718class TestTarifD:19 def test_structure_verifiee(self):20 assert cas.FRAIS_ACCES_JOUR == 0.46154 and cas.PRIX_TRANCHE_1 == 0.07065 and cas.PRIX_TRANCHE_2 == 0.11142 and cas.SEUIL_KWH_JOUR == 402122 def test_facture_sujet(self):23 F = cas.facture_tarif_d(22000.0)24 assert F["acces"] == pytest.approx(168.46, abs=0.005) and F["tranche1_kwh"] == 14600 and F["tranche2_kwh"] == 740025 assert F["energie_t1"] == pytest.approx(1031.49, abs=0.005) and F["energie_t2"] == pytest.approx(824.51, abs=0.005)26 assert F["total"] == pytest.approx(2024.46, abs=0.005)27 assert F["total"] == pytest.approx(F["acces"] + F["energie_t1"] + F["energie_t2"], abs=1e-9)28 assert F["prix_moyen"] == pytest.approx(0.0920, abs=5e-5) and F["prix_marginal"] == 0.111422930 def test_economie_marginale(self):31 # 1 000 kWh retranchés au-dessus du seuil valent 111,42 $ ; à cheval sur le seuil (15 000 → 14 000), 86,96 $32 assert cas.economie_annuelle(26000.0, 25000.0) == pytest.approx(111.42, abs=0.005)33 assert cas.economie_annuelle(15000.0, 14000.0) == pytest.approx(86.96, abs=0.005)34 assert cas.economie_annuelle(22000.0, 18700.0) == pytest.approx(3300 * 0.11142, abs=1e-9)353637class TestMesures:38 def test_thermopompe(self, data):39 m, f = data["mm"]["A"], data["fA"]40 assert m["cout"] == 16500.0 and m["subvention"] == pytest.approx(4320.0) and f["cout_net"] == pytest.approx(12180.0)41 assert m["kwh"] == pytest.approx(3300.0) and f["economie_courante"] == pytest.approx(367.69, abs=0.005)42 assert f["economie_an1"] == pytest.approx(378.72, abs=0.005)43 assert f["van"] == pytest.approx(-7286.54, abs=0.01) and f["tri"] == pytest.approx(-0.0584, abs=5e-5)44 assert f["va_economies"] == pytest.approx(4893.46, abs=0.01) and f["indice_rentabilite"] == pytest.approx(0.402, abs=5e-4)45 assert f["annuite_cout_net"] == pytest.approx(1140.37, abs=0.01)46 assert f["cee_net"] == pytest.approx(0.3456, abs=5e-5) and f["cee_brut"] == pytest.approx(0.4681, abs=5e-5)47 assert f["delai_simple"] is None and f["delai_actualise"] is None4849 def test_isolation(self, data):50 m, f = data["mm"]["B"], data["fB"]51 assert m["cout"] == pytest.approx(4180.0) and f["cout_net"] == pytest.approx(2180.0) and m["kwh"] == pytest.approx(880.0)52 assert f["economie_courante"] == pytest.approx(98.05, abs=0.005) and f["n"] == 3153 assert f["van"] == pytest.approx(224.60, abs=0.01) and f["tri"] == pytest.approx(0.0529, abs=5e-5)54 assert f["delai_simple"] == pytest.approx(16.89, abs=0.005) and f["delai_actualise"] == pytest.approx(27.40, abs=0.005)55 assert f["va_economies"] == pytest.approx(2404.60, abs=0.01) and f["cee_net"] == pytest.approx(0.1512, abs=5e-5)56 assert f["cee_brut"] == pytest.approx(0.2899, abs=5e-5)5758 def test_combinaison_non_additive(self, data):59 mm = data["mm"]60 assert mm["chauffage_kwh"] == pytest.approx(11000.0) and mm["eco_additive_kwh"] == pytest.approx(4180.0)61 assert mm["AB"]["kwh"] == pytest.approx(3916.0) and mm["AB"]["kwh"] < mm["eco_additive_kwh"]62 assert data["fAB"]["van"] == pytest.approx(-8553.09, abs=0.01) and data["fAB"]["va_economies"] == pytest.approx(5806.91, abs=0.01)6364 def test_decision_incrementale(self, data):65 mm, f = data["mm"], data["fI"]66 assert mm["cout_tp_standard"] == pytest.approx(13944.66, abs=0.01) and mm["subvention_tp_standard"] == pytest.approx(1800.0)67 assert mm["surcout_climat_froid"] == pytest.approx(2555.34, abs=0.01) and mm["surcout_net_climat_froid"] == pytest.approx(35.34, abs=0.01)68 assert f["van"] == pytest.approx(3226.97, abs=0.01) and f["va_economies"] == pytest.approx(3262.31, abs=0.01)69 assert f["van"] > 0 and data["fA"]["van"] < 07071 def test_identites(self, data):72 for f in (data["fA"], data["fB"], data["fAB"], data["fI"]):73 # VAN = −coût net + VA des économies ; cumul actualisé final = VAN ; VAN au TRI = 074 assert f["van"] == pytest.approx(-f["cout_net"] + f["va_economies"], abs=1e-9)75 assert f["cumul_actualise"][-1] == pytest.approx(f["van"], abs=1e-9)76 if not math.isnan(f["tri"]): assert fin.van(f["flux"], f["tri"]) == pytest.approx(0.0, abs=1e-6)77 # économies croissantes au taux g78 assert f["flux"][2] / f["flux"][1] == pytest.approx(1 + f["g"], abs=1e-12)79 # annuité : coût net × CRF ; coût de l'énergie économisée = annuité / kWh80 assert f["annuite_cout_net"] == pytest.approx(f["cout_net"] * fin.facteur_recouvrement(f["r"], f["n"]), abs=1e-9)818283class TestSeuilsEtSensibilite:84 def test_seuils_thermopompe(self, data):85 s = cas.seuils(data["mm"]["A"])86 assert s["cout_net_equilibre"] == pytest.approx(data["fA"]["va_economies"], abs=1e-9)87 assert s["subvention_equilibre"] == pytest.approx(11606.54, abs=0.01)88 assert s["kwh_equilibre"] == pytest.approx(8683.0, abs=1.0) and s["fraction_chauffage_equilibre"] == pytest.approx(0.789, abs=5e-4)89 assert s["g_equilibre"] == pytest.approx(0.1440, abs=5e-4)90 assert cas.flux_mesure(data["mm"]["A"], g=s["g_equilibre"])["van"] == pytest.approx(0.0, abs=0.05)9192 def test_grille(self, data):93 G = cas.grille_van(data["mm"]["A"])94 assert G.loc[0.0458, 0.03] == pytest.approx(data["fA"]["van"], abs=1e-9)95 # VAN décroissante dans le taux, croissante dans la croissance du prix96 assert (np.diff(G.values, axis=0) < 0).all() and (np.diff(G.values, axis=1) > 0).all()97 GB = cas.grille_van(data["mm"]["B"])98 assert GB.loc[0.0458, 0.03] == pytest.approx(224.60, abs=0.01) and GB.loc[0.02, 0.0] > 0 and GB.loc[0.10, 0.06] < 099100 def test_duree_et_tornade(self, data):101 D = cas.sensibilite_duree(data["mm"]["A"])102 assert D.loc[15, 0.0458] == pytest.approx(data["fA"]["van"], abs=1e-9) and (np.diff(D.values, axis=0) > 0).all()103 T = cas.sensibilite_tornade(data["mm"]["A"])104 assert len(T) == 7 and all(o["base"] == pytest.approx(data["fA"]["van"], abs=1e-9) for o in T)105 sub = [o for o in T if o["param"].startswith("Subvention")][0]106 assert sub["bas"] == pytest.approx(-11606.54, abs=0.01) and sub["haut"] == pytest.approx(-4906.54, abs=0.01)107108109class TestContribution:110 def test_lectures(self, data):111 L = cas.lectures_contribution(data["mm"]["A"], data["fA"], data["vs"])112 assert data["vs"] == pytest.approx(637952.85, abs=0.01)113 assert L["ratio_va_cout"] == pytest.approx(0.297, abs=5e-4) and L["ratio_va_cout_net"] == pytest.approx(0.402, abs=5e-4)114 assert L["primes_dollars"]["Europe (méta-analyse)"] == pytest.approx(14800.51, abs=0.01)115 assert L["primes_dollars"]["Monde (méta-analyse)"] == pytest.approx(26794.02, abs=0.01)116 assert L["primes_dollars"]["Amérique du Nord (méta-analyse)"] == pytest.approx(34194.27, abs=0.01)117 LB = cas.lectures_contribution(data["mm"]["B"], data["fB"], data["vs"])118 assert LB["ratio_va_cout_net"] == pytest.approx(1.103, abs=5e-4)119120 def test_economies_restantes(self, data):121 m = data["mm"]["A"]122 assert cas.va_economies_restantes(m, 0) == pytest.approx(data["fA"]["va_economies"], abs=1e-9)123 assert cas.va_economies_restantes(m, 5) == pytest.approx(3923.88, abs=0.01)124 assert cas.va_economies_restantes(m, 10) == pytest.approx(2360.94, abs=0.01)125 assert cas.va_economies_restantes(m, 15) == pytest.approx(0.0, abs=1e-9)126127 def test_paires_qualite(self, data):128 P = cas.paires_qualite(data["ventes"], data["modele"])129 assert P["resultat"]["n"] == 30 and len(P["paires"]) == 30130 assert P["mediane_pct"] == pytest.approx(0.0578, abs=5e-5) and P["moyenne_pct"] == pytest.approx(0.0465, abs=5e-5)131 assert P["ecart_type_pct"] == pytest.approx(0.1364, abs=5e-5) and P["erreur_type_pct"] == pytest.approx(0.0249, abs=5e-5)132 assert P["effet_hedonique_pct"] == pytest.approx(0.0630, abs=5e-5) and P["beta_qualite"] == pytest.approx(0.06108, abs=5e-6)133 # chaque vente n'entre que dans une paire ; différence = prix haut − prix bas ajusté134 assert P["paires"]["id_bas"].is_unique and P["paires"]["id_haut"].is_unique135 assert np.allclose(P["paires"]["difference"], P["paires"]["prix_haut"] - P["paires"]["prix_bas_ajuste"])136 C = cas.contribution_renovation(P, data["vs"])137 assert C["contribution_paires"] == pytest.approx(36891.23, abs=0.01) and C["contribution_hedonique"] == pytest.approx(40181.62, abs=0.01)138 assert C["ratio_paires"] == pytest.approx(0.568, abs=5e-4) and C["ratio_hedonique"] == pytest.approx(0.618, abs=5e-4)139 assert C["van_renovation"] == pytest.approx(-28108.77, abs=0.01)140141 def test_fin_de_vie(self):142 F = cas.composantes_fin_de_vie()143 assert len(F) == 5 and list(F["composante"][:2]) == ["Ventilateur récupérateur de chaleur (VRC)", "Entrée asphaltée"]144 assert (F["ratio"] >= 0.90).all() and F["ratio"].iloc[0] == pytest.approx(1.0)145146147@pytest.fixture(scope="module")148def ex():149 return cas.exercices()150151152class TestExercices:153154 def test_ex1(self, ex):155 assert ex["ex1"][18000.0]["total"] == pytest.approx(1578.78, abs=0.005) and ex["ex1"][26000.0]["total"] == pytest.approx(2470.14, abs=0.005)156 assert ex["ex1"][18000.0]["prix_moyen"] == pytest.approx(0.0877, abs=5e-5) and ex["ex1"][26000.0]["prix_moyen"] == pytest.approx(0.0950, abs=5e-5)157 assert ex["ex1"]["economie_1000_kwh_a_26000"] == pytest.approx(111.42, abs=0.005) and ex["ex1"]["economie_1000_kwh_a_15000"] == pytest.approx(86.96, abs=0.005)158159 def test_ex2(self, ex):160 assert ex["ex2"]["standard"] == 1200.0 and ex["ex2"]["froid"] == 2880.0 and ex["ex2"]["froid_60000"] == 6700.0161 assert ex["ex2"]["capacite_plafond_btu"] == pytest.approx(55833.33, abs=0.01)162163 def test_ex3_fenetres(self, ex):164 f = ex["ex3"]["flux"]165 assert ex["ex3"]["mesure"]["subvention"] == 4500.0 and f["cout_net"] == pytest.approx(17811.0)166 assert f["van"] == pytest.approx(-16213.20, abs=0.01) and f["va_economies"] == pytest.approx(1597.80, abs=0.01)167 assert f["cee_net"] == pytest.approx(1.8396, abs=5e-5) and ex["ex3"]["sans_subvention"]["van"] == pytest.approx(-20713.20, abs=0.01)168169 def test_ex4(self, ex):170 assert ex["ex4"]["r458"]["cee_net"] == pytest.approx(0.1512, abs=5e-5) and ex["ex4"]["r458"]["cee_brut"] == pytest.approx(0.2899, abs=5e-5)171 assert ex["ex4"]["r8"]["cee_net"] == pytest.approx(0.2183, abs=5e-5) and ex["ex4"]["sans_sub"]["van"] == pytest.approx(-1775.40, abs=0.01)172173 def test_ex5(self, ex):174 e = ex["ex5"]175 assert e["europe"] == pytest.approx(14800.51, abs=0.01) and e["amerique"] == pytest.approx(34194.27, abs=0.01)176 assert e["va_economies_AB"] == pytest.approx(5806.91, abs=0.01) and e["va_economies_AB_31"] == pytest.approx(10700.48, abs=0.01)177178 def test_ex6(self, ex):179 assert ex["ex6"]["g_equilibre"] == pytest.approx(0.1440, abs=5e-4) and ex["ex6"]["kwh_equilibre"] == pytest.approx(8683.0, abs=1.0)180181 def test_ex7(self, ex):182 assert ex["ex7"]["base"]["resultat"]["n"] == 30 and ex["ex7"]["large"]["resultat"]["n"] == 59 and ex["ex7"]["serre"]["resultat"]["n"] == 4183 assert ex["ex7"]["large"]["mediane_pct"] == pytest.approx(0.0604, abs=5e-5) and ex["ex7"]["serre"]["mediane_pct"] == pytest.approx(0.0092, abs=5e-5)184 assert ex["ex7"]["kennedy"] == pytest.approx(0.06298, abs=5e-6)185