SPB Git
10.9 KB · 177 lines python
1"""Tests du chapitre 13 — lecture des fichiers RÉELS de data/raw (StatCan 18-10-0289-01, 18-10-0205-01, 18-10-0004-01 ;2MAMH RFU 2026 et répertoire du rôle ; SCHL ELL octobre 2025), identités et valeurs citées dans le texte et les solutions.3Tolérances : valeurs d'indices exactes (lecture du CSV) ; pourcentages cités à 0,05 point ; montants des solutions à 1 $ ;4identités à 1e-9."""5import sys, pathlib, math6import numpy as np, pytest7sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases"))8import ch13_sources as cas91011@pytest.fixture(scope="module")12def ipcb():13    return cas.charger_ipcb()141516@pytest.fixture(scope="module")17def schl():18    t = cas.schl_rmr()19    return t, cas.tableau_schl(t)202122class TestIPCB:23    def test_structure(self, ipcb):24        st = cas.structure_ipcb(ipcb)25        assert st["lignes"] == 318954 and st["colonnes"] == 1626        assert st["n_geo"] == 25 and st["n_types"] == 16 and st["n_divisions"] == 2427        assert st["n_vecteurs"] == 8750 and st["produit_theorique"] == 960028        assert st["periode_min"] == "1981-01" and st["periode_max"] == "2026-07" and st["n_periodes"] == 18329        assert st["unite"] == "Indice, 2023=100" and st["valeurs_manquantes"] == 030        assert st["debuts_montreal"]["Bâtiments résidentiels [621]"] == "2017-01"31        assert st["debuts_montreal"]["Tour d'habitations (cinq étages ou plus) [621111]"] == "1988-01"32        assert st["debuts_montreal"]["Bâtiments non résidentiels [622]"] == "1981-01"3334    def test_series_citees(self, ipcb):35        r = cas.resume_ipcb(ipcb)36        m, q = r["Montréal"], r["Québec"]37        assert m["debut"] == "2017T1" and m["fin"] == "2026T3" and m["n"] == 3938        assert m["2017T1"] == 65.5 and m["2025T3"] == 108.5 and m["2026T3"] == 117.8 and m["2023T1"] == 99.939        assert m["var_annuelle_pct"] == pytest.approx(8.57, abs=0.05)40        assert m["facteur_2017T1_2026T3"] == pytest.approx(1.798, abs=5e-4)41        assert m["facteur_2023T1_2026T3"] == pytest.approx(1.179, abs=5e-4)42        assert q["debut"] == "2023T1" and q["n"] == 15 and q["2023T1"] == 98.8 and q["2026T3"] == 124.8 and q["2017T1"] is None43        assert q["var_annuelle_pct"] == pytest.approx(9.19, abs=0.05)44        assert q["facteur_2023T1_2026T3"] == pytest.approx(1.263, abs=5e-4)4546    def test_vecteur_montreal(self, ipcb):47        s = cas.serie_ipcb(ipcb, cas.GEO_MTL)48        assert s["VECTEUR"].iloc[0] == "v1617911460"49        assert cas.serie_ipcb(ipcb, cas.GEO_QC)["VECTEUR"].iloc[0] == "v1617911076"505152class TestIndices:53    def test_rebasage(self, ipcb):54        t = cas.indices_compares(ipcb, ecrire=False); r = cas.resume_indices(t)55        assert r["n_trimestres"] == 39 and t["trimestre"].iloc[0] == "2017T1" and t["trimestre"].iloc[-1] == "2026T3"56        for c in ("ipcb_mtl_resid", "ipc_quebec", "ipln_mtl"):57            assert t.loc[0, c + "_base2017"] == pytest.approx(100.0, abs=1e-9)58        assert r["ipcb_2026T3_base2017"] == pytest.approx(179.8, abs=0.05)59        assert r["ipln_2026T3_base2017"] == pytest.approx(159.0, abs=0.05)60        assert r["ipc_2026T3_base2017"] == pytest.approx(132.2, abs=0.05)61        # invariance du ratio d'indexation au rebasage (eq. 13.11)62        assert t["ipcb_mtl_resid_base2017"].iloc[-1] / t["ipcb_mtl_resid_base2017"].iloc[0] == pytest.approx(t["ipcb_mtl_resid"].iloc[-1] / t["ipcb_mtl_resid"].iloc[0], rel=1e-12)63        assert r["ipc_quebec_2026T3_moy"] == pytest.approx(167.2, abs=0.05) and r["ipln_mtl_2026T3_moy"] == pytest.approx(159.5, abs=0.05)6465    def test_fichier_processed(self, ipcb):66        t = cas.indices_compares(ipcb, ecrire=True)67        assert (cas.PROCESSED / "ch13_indices.csv").exists() and len(t) == 39686970class TestDictionnaireEtRoles:71    def test_dictionnaire(self):72        d = cas.dictionnaire_role()73        assert d["n_pages"] == 2 and d["n_codes_uniques"] == 62 and d["n_repetitifs"] == 2474        assert d["par_section"] == {1: 21, 2: 2, 3: 17, 4: 5, 5: 17}75        assert all(d["champs_cles_presents"].values())76        assert d["date_document"] == "24 mars 2022"7778    def test_index_roles(self):79        ix = cas.index_roles()80        assert ix["n_municipalites"] == 1134 and ix["colonnes"] == ["code géographique", "nom du territoire", "lien"]81        assert ix["gatineau"][-1].endswith("RL81017_2026.xml") and ix["montreal"][-1].endswith("RL66023_2026.xml") and ix["quebec"][-1].endswith("RL23027_2026.xml")828384class TestRFU:85    def test_structure_et_villes(self):86        rf = cas.rfu_2026()87        assert rf["n_lignes"] == 1132 and rf["n_colonnes"] == 37 and rf["types_org"] == {"Municipalité locale": 1132} and rf["n_postes_decrits"] == 2488        v = rf["villes"]89        assert v["Montréal"]["imposable_uniformise"] == pytest.approx(430.75e9, abs=0.005e9)90        assert v["Québec"]["imposable_uniformise"] == pytest.approx(105.22e9, abs=0.005e9)91        assert v["Gatineau"]["imposable_uniformise"] == pytest.approx(60.01e9, abs=0.005e9)92        assert v["Montréal"]["facteur_redressement"] == 1.00 and v["Québec"]["facteur_redressement"] == 1.08 and v["Gatineau"]["facteur_redressement"] == 1.0293        for d in v.values():  # identité : somme des six postes = évaluation imposable uniformisée94            assert abs(d["ecart_somme_vs_imposable"]) < 1.095            assert sum(d["parts_pct"].values()) == pytest.approx(100.0, abs=1e-6)96        assert v["Gatineau"]["parts_pct"]["Résidences d'un logement"] == pytest.approx(56.5, abs=0.05)97        assert v["Montréal"]["parts_pct"]["Immeubles à logements et condominiums"] == pytest.approx(60.6, abs=0.05)98        assert v["Québec"]["parts_pct"]["Résidences d'un logement"] == pytest.approx(40.2, abs=0.05)99        assert v["Québec"]["parts_pct"]["Immeubles à logements et condominiums"] == pytest.approx(38.8, abs=0.05)100        assert rf["total_rfu"] == pytest.approx(2077.9e9, abs=0.05e9) and rf["part_3_villes_rfu_pct"] == pytest.approx(31.1, abs=0.05)101102103class TestSCHL:104    def test_tableau(self, schl):105        t, ts = schl106        assert t["n_onglets"] == 20107        attendu = {  # inocc 2024, inocc 2025, loyer 2 ch. 2024, 2025, univers 2025, var. estimative 2 ch.108            "Montréal": (2.1, 2.9, 1176, 1346, 667266, 7.2), "Québec": (0.9, 2.4, 1159, 1277, 118693, 6.1),109            "Gatineau": (1.9, 3.8, 1353, 1460, 33553, 4.7), "Sherbrooke": (1.4, 2.7, 1002, 1062, 41949, 9.8),110            "Trois-Rivières": (0.9, 2.7, 885, 1016, 22505, 6.6), "Saguenay": (1.6, 1.3, 882, 985, 16241, 10.9)}111        for rmr, (i24, i25, l24, l25, u, ve) in attendu.items():112            r = ts.loc[rmr]113            assert r["inocc_2024"] == i24 and r["inocc_2025"] == i25 and r["loyer_2ch_2024"] == l24 and r["loyer_2ch_2025"] == l25114            assert r["univers_2025"] == u and r["var_estim_2ch_pct"] == ve115            assert r["var_brute_2ch_pct"] == pytest.approx((l25 / l24 - 1) * 100, rel=1e-12)116            assert r["logements_vacants_implicites"] == pytest.approx(u * i25 / 100, rel=1e-12)117        assert ts.loc["Montréal", "var_brute_2ch_pct"] == pytest.approx(14.5, abs=0.05)118        assert ts.loc["Montréal", "logements_vacants_implicites"] == pytest.approx(19351, abs=0.5)119        assert ts.loc["Saguenay", "logements_vacants_implicites"] == pytest.approx(211, abs=0.5)120        assert ts.loc["Sherbrooke", "var_brute_2ch_pct"] < ts.loc["Sherbrooke", "var_estim_2ch_pct"]  # seule RMR où l'effet de composition est négatif121        assert ts.loc["Montréal", "inocc_q"] == "a" and ts.loc["Gatineau", "inocc_q"] == "c" and ts.loc["Trois-Rivières", "inocc_q"] == "b"122        assert ts.loc["Montréal", "univers_2ch_2025"] == 362373123124125class TestDatation:126    def test_biais(self):127        bd = cas.biais_datation().set_index(["croissance_annuelle_pct", "delai_mois"])["biais_pct"]128        assert bd[(6.0, 3)] == pytest.approx(1.47, abs=0.005) and bd[(10.0, 3)] == pytest.approx(2.41, abs=0.005)129        assert bd[(10.0, 6)] == pytest.approx(4.88, abs=0.005) and bd[(-3.0, 6)] == pytest.approx(-1.51, abs=0.005)130        assert (bd.xs(0.0, level=0) == 0).all()131        # approximation du premier ordre : b ≈ g·δ/12132        assert bd[(3.0, 1)] == pytest.approx(0.25, abs=0.01)133134    def test_proposition_signe(self):135        for g in (-0.03, 0.03, 0.10):136            b = (1 + g) ** (-3 / 12) - 1137            assert math.copysign(1, b) == -math.copysign(1, g)138139140class TestExercices:141    def test_ex1_registre(self):142        r = cas.exercice_registre()143        assert r["consultation"] == 50 and r["etats"] == 96 and r["total"] == 146144145    def test_ex2_prix_corrige(self):146        ex = cas.prix_corrige(612000, 12000, 0.06, 75 / 30.4375)147        assert ex["prix_immeuble"] == 600000 and ex["facteur_datation"] == pytest.approx(1.0120, abs=5e-5)148        assert ex["ecart_dollars"] == pytest.approx(7222, abs=1)149150    def test_ex4_indexation(self, schl):151        _, ts = schl152        brute = ts.loc["Montréal", "loyer_2ch_2025"] / ts.loc["Montréal", "loyer_2ch_2024"]153        assert 1300 * brute == pytest.approx(1487.93, abs=0.01) and 1300 * 1.072 == pytest.approx(1393.60, abs=0.01)154        ecart = 1300 * (brute - 1.072)155        assert ecart == pytest.approx(94.33, abs=0.01) and ecart * 12 == pytest.approx(1132, abs=0.5)156        assert ecart * 12 * 12 / 0.045 == pytest.approx(301840, abs=5)157158    def test_ex5_ipcb_quebec(self, ipcb):159        e = cas.exercice_ipcb_quebec(ipcb)160        assert e["Montréal"]["ratio_2023T1_2026T3"] == pytest.approx(1.1792, abs=5e-5) and e["Montréal"]["tcac_2023T1_2026T3_pct"] == pytest.approx(4.82, abs=0.005)161        assert e["Québec"]["ratio_2023T1_2026T3"] == pytest.approx(1.2632, abs=5e-5) and e["Québec"]["tcac_2023T1_2026T3_pct"] == pytest.approx(6.90, abs=0.005)162        assert e["Composite"]["ratio_2023T1_2026T3"] == pytest.approx(1.1391, abs=5e-5) and e["Composite"]["tcac_2023T1_2026T3_pct"] == pytest.approx(3.79, abs=0.005)163        assert e["Montréal"]["ratio_2019T3_2026T3"] == pytest.approx(1.6453, abs=5e-5) and e["Composite"]["ratio_2019T3_2026T3"] == pytest.approx(1.7406, abs=5e-5)164        assert e["Québec"]["ratio_2019T3_2026T3"] is None and e["Composite"]["var_annuelle_pct"] == pytest.approx(2.67, abs=0.005)165        assert 350000 * e["Québec"]["ratio_2023T1_2026T3"] == pytest.approx(442105, abs=1)166        assert 350000 * e["Montréal"]["ratio_2023T1_2026T3"] == pytest.approx(412713, abs=1)167        assert 350000 * e["Composite"]["ratio_2023T1_2026T3"] == pytest.approx(398671, abs=1)168        assert e["Montréal"]["ratio_2023T1_2026T3"] / e["Québec"]["ratio_2023T1_2026T3"] - 1 == pytest.approx(-0.066, abs=5e-4)169170    def test_ex6_couverture(self):171        c = cas.exercice_couverture()172        assert c["couverture"] == pytest.approx(0.696, abs=5e-4) and c["appariement"] == pytest.approx(0.881, abs=5e-4)173        assert c["moyenne_marche"] == pytest.approx(493280, abs=0.01) and c["biais_courtier_dollars"] == pytest.approx(16720, abs=0.01)174        assert c["biais_courtier_pct"] == pytest.approx(3.39, abs=0.005)175        # identité de composition (eq. 13.6)176        assert c["poids_courtier"] * 510000 + (1 - c["poids_courtier"]) * 455000 == pytest.approx(c["moyenne_marche"], rel=1e-12)177
Files · 9dc3847990198c3379e1a64231cde8ea5f1d747e
Menu

Theme