SPB Git

Ch. 11 livré et validé (compilation isolée, tests, fusion des registres)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Oct 31, 2026) parent a58cc97 Browse files

48 changed files +2,252 −35

48 changed files
Unified Split
added analysis/python/cases/ch28_depenses.py +313 −0
@@ -0,0 +1,313 @@
1+#!/usr/bin/env python3
2+"""Chapitre 28 — Dépenses, réserves, CAPEX et RNE : calculs du cas 3 (12 logements, Rosemont) et du cas 2 (triplex, Limoilou).
3+DONNÉES SYNTHÉTIQUES (graine 20261031).
4+
5+Tous les chiffres cités dans manuscript/chapters/ch28.tex et solutions/ch28.tex sortent d'ici. Les calculs partagés
6+(revenus, dépenses normalisées, RNE) viennent de traite.cas3 et traite.cas2, pour coïncider avec les chapitres 29, 30,
7+32, 46 et 47 ; les formules financières viennent de traite.finance. Aucune formule partagée n'est réécrite ; les
8+calculs propres au chapitre (classement, normalisation alternative, équivalence réserve/CAPEX, sensibilités) sont ici.
9+
10+Usage (racine du dépôt) : PYTHONPATH=analysis/python python3 analysis/python/cases/ch28_depenses.py
11+"""
12+from __future__ import annotations
13+import sys, pathlib
14+import numpy as np, pandas as pd
15+sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1]))
16+from traite import cas3, cas2, finance as fin
17+
18+ROOT = pathlib.Path(__file__).resolve().parents[3]
19+
20+# Taux global médian extrait au chapitre 29 (traite.cas3.taux_extraits ; revenu net APRÈS réserve). Repris ici pour les
21+# démonstrations de cohérence ; recalculé par calculer() et vérifié par le test.
22+GROUPES = { # regroupement des postes pour les figures et les ratios de référence
23+ "Taxes municipales": "Taxes foncières", "Taxe scolaire": "Taxes foncières", "Assurances": "Assurances",
24+ "Électricité (aires communes)": "Énergie des aires communes", "Chauffage (aires communes, gaz)": "Énergie des aires communes",
25+ "Eau et égouts (taxe)": "Taxes foncières", "Entretien et réparations": "Entretien et réparations",
26+ "Conciergerie": "Conciergerie et services", "Déneigement et paysagement": "Conciergerie et services",
27+ "Déneigement et entretien extérieur": "Conciergerie et services",
28+ "Frais juridiques et comptables": "Honoraires et publicité", "Publicité et location": "Honoraires et publicité",
29+ "Gestion (4 % du RBE)": "Gestion", "Gestion (propriétaire-occupant : imputée 4 %)": "Gestion",
30+ "Réserve de remplacement (par logement)": "Réserve de remplacement", "Réserve de remplacement": "Réserve de remplacement",
31+}
32+ORDRE_GROUPES = ["Taxes foncières", "Assurances", "Énergie des aires communes", "Entretien et réparations",
33+ "Conciergerie et services", "Honoraires et publicité", "Gestion", "Réserve de remplacement"]
34+
35+# Composantes à vie courte couvertes par la réserve — HYPOTHÈSES PÉDAGOGIQUES déclarées (coût unitaire par logement, durée
36+# de vie en années). Elles servent uniquement à illustrer les deux méthodes de calcul d'une réserve (linéaire et fonds
37+# d'amortissement) ; elles ne proviennent d'aucun barème publié.
38+COMPOSANTES_RESERVE = [
39+ ("Cuisinière et réfrigérateur", 2200.0, 12),
40+ ("Chauffe-eau", 1100.0, 10),
41+ ("Revêtements de sol", 3600.0, 20),
42+ ("Peinture et finition intérieure", 1500.0, 7),
43+]
44+
45+
46+def _groupes(detail: pd.DataFrame, col: str = "normalise") -> pd.Series:
47+ g = detail.assign(groupe=detail["poste"].map(GROUPES)).groupby("groupe")[col].sum()
48+ return g.reindex(ORDRE_GROUPES).fillna(0.0)
49+
50+
51+def historique_cas3(dep: pd.DataFrame) -> pd.DataFrame:
52+ """Pivot 2023-2025 par poste, avec variation totale 2025/2023 et taux de croissance annuel composé."""
53+ piv = dep.pivot(index="poste", columns="annee", values="montant").astype(float)
54+ ordre = ["Taxes municipales", "Taxe scolaire", "Assurances", "Électricité (aires communes)", "Chauffage (aires communes, gaz)",
55+ "Eau et égouts (taxe)", "Entretien et réparations", "Conciergerie", "Déneigement et paysagement",
56+ "Frais juridiques et comptables", "Publicité et location", "Réserve de remplacement (par logement)"]
57+ piv = piv.reindex(ordre)
58+ piv.loc["Total"] = piv.sum()
59+ with np.errstate(divide="ignore", invalid="ignore"):
60+ piv["var_2025_2023"] = piv[2025] / piv[2023] - 1
61+ piv["tcac"] = (piv[2025] / piv[2023]) ** 0.5 - 1
62+ return piv
63+
64+
65+def normalisation_alternative(dep: pd.DataFrame, g: float = 0.03, annee_cible: int = 2026) -> pd.DataFrame:
66+ """Pour les postes indexés : (a) dernière année × (1+g) [règle du livre] ; (b) moyenne des trois exercices, chacun
67+ ramené en dollars de l'année cible au taux g (moyenne indexée). Permet de comparer les deux conventions."""
68+ piv = dep.pivot(index="poste", columns="annee", values="montant").astype(float)
69+ rows = []
70+ for poste, r in piv.iterrows():
71+ indexes = {an: r[an] * (1 + g) ** (annee_cible - an) for an in (2023, 2024, 2025)}
72+ rows.append({"poste": poste, "derniere_indexee": indexes[2025], "moyenne_indexee": float(np.mean(list(indexes.values()))),
73+ "min_indexe": min(indexes.values()), "max_indexe": max(indexes.values())})
74+ out = pd.DataFrame(rows).set_index("poste")
75+ out["ecart"] = out["moyenne_indexee"] - out["derniere_indexee"]
76+ return out
77+
78+
79+def reserve_par_composantes(composantes=COMPOSANTES_RESERVE, nb_logements: int = 12, taux: float = 0.03) -> dict:
80+ """Réserve annuelle par la méthode linéaire (coût/durée) et par le fonds d'amortissement (coût × SFF au taux donné)."""
81+ rows = []
82+ for nom, cout, vie in composantes:
83+ lin = cout / vie
84+ sff = cout * fin.facteur_fonds_amortissement(taux, vie)
85+ rows.append({"composante": nom, "cout_par_logement": cout, "duree_vie": vie, "lineaire_par_logement": lin, "fonds_par_logement": sff,
86+ "lineaire_immeuble": lin * nb_logements, "fonds_immeuble": sff * nb_logements})
87+ df = pd.DataFrame(rows)
88+ return {"detail": df, "lineaire_par_logement": float(df["lineaire_par_logement"].sum()), "fonds_par_logement": float(df["fonds_par_logement"].sum()),
89+ "lineaire_immeuble": float(df["lineaire_immeuble"].sum()), "fonds_immeuble": float(df["fonds_immeuble"].sum()), "taux": taux}
90+
91+
92+def demonstration_reserve(noi_apres: float, reserve: float, R_apres: float) -> dict:
93+ """Cohérence réserve/taux : V cohérent ; taux « avant réserve » équivalent R' = R + RR/V ; valeur erronée si R' est
94+ appliqué au revenu après réserve ; écart exact ΔV = RR/R' ; erreur relative −RR/(RNE+RR)."""
95+ V = fin.capitalisation_directe(noi_apres, R_apres)
96+ R_avant = fin.taux_global_extrait(noi_apres + reserve, V)
97+ V_err = fin.capitalisation_directe(noi_apres, R_avant)
98+ V_err_inverse = fin.capitalisation_directe(noi_apres + reserve, R_apres) # taux après réserve sur revenu avant réserve
99+ return {"V": V, "R_apres": R_apres, "R_avant": R_avant, "ecart_taux_pb": (R_avant - R_apres) * 1e4, "V_err": V_err,
100+ "delta_V": V - V_err, "delta_V_formule": reserve / R_avant, "erreur_relative": V_err / V - 1,
101+ "erreur_relative_formule": -reserve / (noi_apres + reserve), "approx_reserve_sur_R": reserve / R_apres,
102+ "V_err_inverse": V_err_inverse, "erreur_relative_inverse": V_err_inverse / V - 1}
103+
104+
105+def equivalence_capex(hyp: dict, r: float | None = None, T: int | None = None) -> dict:
106+ """Les CAPEX programmés du dossier (flux actualisés) convertis en charge annuelle équivalente sur l'horizon : montre
107+ ce qu'une réserve « pour travaux lourds » représenterait si on la comptait en plus des CAPEX (double comptage)."""
108+ r = hyp["taux_actualisation"] if r is None else r; T = hyp["horizon_ans"] if T is None else T
109+ an0 = int(hyp["date_evaluation"][:4])
110+ flux = [0.0] * (T + 1)
111+ for an, m in hyp["capex"].items():
112+ t = int(an) - an0 + 1
113+ if 1 <= t <= T: flux[t] = float(m)
114+ va = fin.van(flux, r) # flux[0] = 0 à t = 0
115+ annuite = va * fin.facteur_recouvrement(r, T)
116+ total = float(sum(hyp["capex"].values()))
117+ return {"capex": {int(k): float(v) for k, v in hyp["capex"].items()}, "total": total, "va": va, "annuite_equivalente": annuite,
118+ "moyenne_simple": total / T, "r": r, "T": T}
119+
120+
121+def classement(detail: pd.DataFrame, rbe: float, nb_log: int, surface_m2: float) -> dict:
122+ d = detail.copy()
123+ fixes = float(d.loc[d["type"] == "fixe", "normalise"].sum()); variables = float(d.loc[d["type"] == "variable", "normalise"].sum())
124+ reserve = float(d.loc[d["type"] == "réserve", "normalise"].sum()); total = fixes + variables + reserve
125+ gestion = float(d.loc[d["poste"].str.startswith("Gestion"), "normalise"].sum())
126+ return {"fixes": fixes, "variables": variables, "variables_hors_gestion": variables - gestion, "gestion": gestion, "reserve": reserve, "total": total,
127+ "part_fixes": fixes / total, "part_variables": variables / total, "part_reserve": reserve / total,
128+ "fixes_pct_rbe": fixes / rbe, "variables_pct_rbe": variables / rbe, "reserve_pct_rbe": reserve / rbe, "total_pct_rbe": total / rbe,
129+ "total_par_logement": total / nb_log, "total_par_m2": total / surface_m2, "groupes": _groupes(d),
130+ "groupes_pct_rbe": _groupes(d) / rbe, "groupes_par_logement": _groupes(d) / nb_log}
131+
132+
133+def rbe_fonction_loyer(loyer_moyen: float, nb_log: int, v: float, autres: float) -> float:
134+ return fin.revenu_brut_effectif(loyer_moyen * nb_log * 12, v, autres)
135+
136+
137+def ratio_iso_depenses(loyer_moyen, base: dict, hyp: dict, nb_log: int = 12, gestion: float = 0.04):
138+ """Ratio de dépenses du sujet si son loyer moyen était L (dépenses hors gestion et réserve inchangées, gestion en % du RBE)."""
139+ L = np.asarray(loyer_moyen, dtype=float)
140+ rbe = rbe_fonction_loyer(L, nb_log, hyp["taux_inoccupation_pertes"], hyp["autres_revenus_annuels"])
141+ de_hors_gestion = base["depenses_hors_reserve"] - gestion * base["rbe"]
142+ return (de_hors_gestion + gestion * rbe + base["reserve"]) / rbe
143+
144+
145+def sensibilites(base: dict, hyp: dict, R: float, nb_log: int = 12) -> dict:
146+ """Sensibilité du RNE (et de V = RNE/R à taux inchangé) à la réserve par logement, au taux de taxe et au taux de gestion."""
147+ rbe = base["rbe"]; de_hors_res = base["depenses_hors_reserve"]; res0 = base["reserve"]; noi0 = base["rne"]; V0 = noi0 / R
148+ role = hyp["valeur_role_2026"]; taxe0 = role * hyp["taux_taxe_fonciere"]; gestion0 = 0.04 * rbe
149+ # réserve par logement
150+ rows_r = []
151+ for rpl in [0, 100, 300, 500, 675.25, 900, 1200]:
152+ res = rpl * nb_log; noi = fin.rne(rbe, de_hors_res, res)
153+ rows_r.append({"reserve_par_logement": rpl, "reserve": res, "rne": noi, "V": noi / R, "dV_pct": noi / noi0 - 1, "ratio": (de_hors_res + res) / rbe})
154+ # taux de taxe municipale ($/100 $), rôle inchangé
155+ rows_t = []
156+ scen = [("Montréal, taxe générale 2026 (base)", hyp["taux_taxe_fonciere"]),
157+ ("Compte 2025 indexé de 5,3 % (Rosemont)", 10631 * 1.053 / role),
158+ ("Gatineau, six logements ou plus (6,964 $/1 000 $)", 0.006964),
159+ ("Québec, six logements et plus (0,7697 $/100 $)", 0.007697),
160+ ("Taux + 10 points de base", hyp["taux_taxe_fonciere"] + 0.001)]
161+ for nom, tau in scen:
162+ taxe = role * tau; noi = fin.rne(rbe, de_hors_res - taxe0 + taxe, res0)
163+ rows_t.append({"scenario": nom, "taux_par_100": tau * 100, "taxe": taxe, "delta_taxe": taxe - taxe0, "rne": noi, "V": noi / R, "dV_pct": noi / noi0 - 1})
164+ # taux de gestion
165+ rows_g = []
166+ for m in [0.0, 0.03, 0.04, 0.05, 0.06, 0.10]:
167+ noi = fin.rne(rbe, de_hors_res - gestion0 + m * rbe, res0)
168+ rows_g.append({"gestion_pct": m, "gestion": m * rbe, "rne": noi, "V": noi / R, "dV_pct": noi / noi0 - 1})
169+ # dérivée : un dollar de dépense = 1/R dollars de valeur
170+ return {"reserve": pd.DataFrame(rows_r), "taxe": pd.DataFrame(rows_t), "gestion": pd.DataFrame(rows_g), "dV_dDE": -1.0 / R, "V0": V0,
171+ "elasticite_rne_depenses": -(de_hors_res + res0) / noi0}
172+
173+
174+def cas2_detail() -> dict:
175+ d = cas2.charger(); b = cas2.rne(d["loyers"], d["depenses"]); s = d["sujet"]
176+ det = b["detail"].copy(); det["type_livre"] = det["type"].replace({"imputée": "variable"})
177+ det_cl = pd.DataFrame({"poste": det["poste"], "normalise": det["normalise"], "type": det["type_livre"]})
178+ cl = classement(det_cl, b["rbe"], s["nb_logements"], s["superficie_habitable_m2"])
179+ # vérification des taxes de Québec 2026 : taux 1-5 logements + tarifs par logement ; taxe scolaire sur (rôle − 25 000 $)
180+ role = s["valeur_role_2025"]; n = s["nb_logements"]
181+ taxe_gen = role * 0.007464; tarifs = n * (386 + 195); taxe_mun_recalc = taxe_gen + tarifs
182+ scol_recalc = max(role - 25000, 0) * 0.0007899
183+ ind = cas2.indicateurs_marche(cas2.comparables_plex(d["ventes"], s))
184+ return {**d, "base": b, "indicateurs": ind, "classement": cl, "detail": det, "rne_par_logement": b["rne"] / n, "rne_par_m2": b["rne"] / s["superficie_habitable_m2"],
185+ "taxes_quebec_recalc": {"taxe_generale": taxe_gen, "tarifs": tarifs, "total": taxe_mun_recalc, "declaree": 5900.0, "ecart": 5900.0 - taxe_mun_recalc,
186+ "scolaire_recalc": scol_recalc, "scolaire_declaree": 470.0},
187+ "gestion_imputee": float(det.loc[det["type"] == "imputée", "normalise"].sum()), "reserve_par_logement": b["reserve"] / n}
188+
189+
190+def calculer() -> dict:
191+ d = cas3.charger(); etat, dep, hyp, sujet, comps = d["etat"], d["dep"], d["hyp"], d["sujet"], d["comps"]
192+ base = cas3.rne(etat, dep, hyp); tx = cas3.taux_extraits(comps); R = tx["mediane"]
193+ nb = sujet["nb_logements"]; surf = sujet["superficie_locative_m2"]
194+ hist = historique_cas3(dep); alt = normalisation_alternative(dep, hyp["croissance_depenses"])
195+ cl = classement(base["detail"], base["rbe"], nb, surf)
196+ det = base["detail"].copy(); det["regle"] = det["poste"].map(lambda p: "rôle × taux" if p.startswith("Tax") else ("maintenue" if p.startswith("Réserve") else ("% du RBE" if p.startswith("Gestion") else "× 1,03")))
197+ det["pct_rbe"] = det["normalise"] / base["rbe"]; det["par_logement"] = det["normalise"] / nb
198+ demo = demonstration_reserve(base["rne"], base["reserve"], R)
199+ res_comp = reserve_par_composantes(nb_logements=nb, taux=hyp["croissance_depenses"])
200+ eq_capex = equivalence_capex(hyp)
201+ sens = sensibilites(base, hyp, R, nb)
202+ # revenu net et ratio si les loyers étaient au marché (même convention que l'exercice 29.7)
203+ rbe_m = fin.revenu_brut_effectif(base["rbp_marche"], hyp["taux_inoccupation_pertes"], hyp["autres_revenus_annuels"])
204+ de_m = base["depenses_hors_reserve"] - 0.04 * base["rbe"] + 0.04 * rbe_m
205+ noi_m = fin.rne(rbe_m, de_m, base["reserve"])
206+ marche = {"rbe": rbe_m, "depenses_hors_reserve": de_m, "rne": noi_m, "ratio": (de_m + base["reserve"]) / rbe_m,
207+ "loyer_moyen": base["rbp_marche"] / 12 / nb, "loyer_moyen_contrat": base["rbp_contrat"] / 12 / nb}
208+ comps_stats = {"n": len(comps), "ratio_median": float(comps["ratio_depenses"].median()), "ratio_q1": float(comps["ratio_depenses"].quantile(0.25)),
209+ "ratio_q3": float(comps["ratio_depenses"].quantile(0.75)), "ratio_min": float(comps["ratio_depenses"].min()), "ratio_max": float(comps["ratio_depenses"].max()),
210+ "corr_ratio_loyer": float(comps[["ratio_depenses", "loyer_mensuel_moyen"]].corr().iloc[0, 1]),
211+ "loyer_median": float(comps["loyer_mensuel_moyen"].median())}
212+ # revenu net historique 2025 « tel que déclaré » (sans gestion, sans indexation) pour l'erreur du RNE historique
213+ dep_2025 = float(hist.loc["Total", 2025]); noi_hist = base["rbe"] - dep_2025
214+ return {**d, "base": base, "R": R, "taux": tx, "nb_logements": nb, "surface_m2": surf, "historique": hist, "alternative": alt, "classement": cl, "detail": det,
215+ "demo_reserve": demo, "reserve_composantes": res_comp, "equivalence_capex": eq_capex, "sensibilites": sens, "marche": marche, "comps_stats": comps_stats,
216+ "rne_par_logement": base["rne"] / nb, "rne_par_m2": base["rne"] / surf, "rne_pct_rbe": base["rne"] / base["rbe"],
217+ "depenses_2025_declarees": dep_2025, "rne_historique_2025": noi_hist, "ecart_normalisation": base["depenses_totales"] - dep_2025,
218+ "V_historique": noi_hist / R, "V": base["rne"] / R, "cas2": cas2_detail()}
219+
220+
221+def exercices(res: dict) -> dict:
222+ """Chiffres des solutions (solutions/ch28.tex)."""
223+ base, hyp, R = res["base"], res["hyp"], res["R"]
224+ ex = {}
225+ # 28.1 — classement et ratios d'un huit logements (données de l'énoncé)
226+ rbe1 = 8 * 1250 * 12 * (1 - 0.04) + 3600
227+ postes1 = {"Taxes municipales": 1450000 * 0.004631, "Taxe scolaire": 1450000 * 0.0007899, "Assurances": 7400.0, "Électricité des aires communes": 2100.0,
228+ "Entretien et réparations": 6800.0, "Déneigement et paysagement": 3900.0, "Gestion (5 % du RBE)": 0.05 * rbe1, "Réserve (600 $/log.)": 8 * 600.0}
229+ fixes1 = postes1["Taxes municipales"] + postes1["Taxe scolaire"] + postes1["Assurances"]; res1 = postes1["Réserve (600 $/log.)"]
230+ var1 = sum(postes1.values()) - fixes1 - res1
231+ ex["ex1"] = {"rbe": rbe1, "postes": postes1, "fixes": fixes1, "variables": var1, "reserve": res1, "total": fixes1 + var1 + res1,
232+ "rne": rbe1 - fixes1 - var1 - res1, "ratio": (fixes1 + var1 + res1) / rbe1, "rne_par_logement": (rbe1 - fixes1 - var1 - res1) / 8,
233+ "part_fixes": fixes1 / (fixes1 + var1 + res1)}
234+ # 28.2 — normalisation : dernière année indexée vs moyenne indexée pour trois postes du cas 3
235+ alt = res["alternative"]
236+ ex["ex2"] = {p: {"derniere": alt.loc[p, "derniere_indexee"], "moyenne": alt.loc[p, "moyenne_indexee"], "ecart": alt.loc[p, "ecart"]}
237+ for p in ["Assurances", "Entretien et réparations", "Conciergerie"]}
238+ ex["ex2"]["ecart_total"] = sum(v["ecart"] for k, v in ex["ex2"].items() if isinstance(v, dict))
239+ ex["ex2"]["dV"] = ex["ex2"]["ecart_total"] / R
240+ # 28.3 — taxes : rôle × taux vs compte indexé ; effet sur RNE et V
241+ role = hyp["valeur_role_2026"]; taxe_base = role * hyp["taux_taxe_fonciere"]; taxe_idx = 10631 * 1.053
242+ ex["ex3"] = {"taxe_base": taxe_base, "taxe_indexee": taxe_idx, "ecart": taxe_idx - taxe_base, "rne_alt": base["rne"] - (taxe_idx - taxe_base),
243+ "V_base": base["rne"] / R, "V_alt": (base["rne"] - (taxe_idx - taxe_base)) / R, "dV": -(taxe_idx - taxe_base) / R,
244+ "scolaire_sans_exemption": role * hyp["taux_taxe_scolaire"], "scolaire_avec_exemption": (role - 25000) * hyp["taux_taxe_scolaire"],
245+ "ecart_exemption": 25000 * hyp["taux_taxe_scolaire"]}
246+ # 28.4 — réserve : linéaire vs fonds d'amortissement à 3 % et à 5 %
247+ rc3 = reserve_par_composantes(taux=0.03); rc5 = reserve_par_composantes(taux=0.05)
248+ ex["ex4"] = {"lineaire": rc3["lineaire_par_logement"], "fonds_3": rc3["fonds_par_logement"], "fonds_5": rc5["fonds_par_logement"],
249+ "lineaire_immeuble": rc3["lineaire_immeuble"], "fonds_3_immeuble": rc3["fonds_immeuble"], "convention": base["reserve"] / 12,
250+ "detail3": rc3["detail"]}
251+ # 28.5 — cohérence réserve/taux sur un comparable de 20 logements
252+ noi_c, rr_c, P_c = 182000.0, 20 * 675.0, 4100000.0
253+ R_c_apres = noi_c / P_c; R_c_avant = (noi_c + rr_c) / P_c
254+ ex["ex5"] = {"R_apres": R_c_apres, "R_avant": R_c_avant, "ecart_pb": (R_c_avant - R_c_apres) * 1e4,
255+ "V_coherent": base["rne"] / R_c_apres, "V_err": base["rne"] / R_c_avant, "erreur_rel": (base["rne"] / R_c_avant) / (base["rne"] / R_c_apres) - 1,
256+ "delta_V": base["rne"] / R_c_apres - base["rne"] / R_c_avant, "delta_formule_sujet": base["reserve"] / R_c_avant,
257+ "ratio_reserve_comparable": rr_c / (noi_c + rr_c), "ratio_reserve_sujet": base["reserve"] / (base["rne"] + base["reserve"]),
258+ "V_avant_sur_avant": (base["rne"] + base["reserve"]) / R_c_avant}
259+ # 28.6 — gestion imputée et propriétaire-occupant (cas 2)
260+ c2 = res["cas2"]; b2 = c2["base"]
261+ noi_sans_gestion = b2["rne"] + c2["gestion_imputee"]
262+ ex["ex6"] = {"gestion": c2["gestion_imputee"], "rne_avec": b2["rne"], "rne_sans": noi_sans_gestion, "ratio_avec": b2["ratio_depenses"],
263+ "ratio_sans": (b2["depenses_totales"] - c2["gestion_imputee"]) / b2["rbe"], "gestion_5": 0.05 * b2["rbe"], "rne_5": b2["rne"] - 0.01 * b2["rbe"],
264+ "taux_median_plex": c2["indicateurs"]["taux_median"], "V_avec": b2["rne"] / c2["indicateurs"]["taux_median"],
265+ "V_sans": noi_sans_gestion / c2["indicateurs"]["taux_median"], "surevaluation": noi_sans_gestion / b2["rne"] - 1}
266+ # 28.7 — CAPEX vs réserve : annuité équivalente et double comptage
267+ eq = res["equivalence_capex"]
268+ ex["ex7"] = {**eq, "rne_double": base["rne"] - eq["annuite_equivalente"], "V_double": (base["rne"] - eq["annuite_equivalente"]) / R,
269+ "erreur_rel_double": -eq["annuite_equivalente"] / base["rne"], "va_a_R": None}
270+ # valeur actuelle de la réserve perpétuelle vs CAPEX perpétuels : composante de 2 200 $/log. tous les 12 ans à 3 %
271+ C = 2200.0 * 12; N = 12; r = 0.03
272+ ex["ex7"]["va_capex_perpetuel"] = C / ((1 + r) ** N - 1); ex["ex7"]["reserve_equiv"] = C * fin.facteur_fonds_amortissement(r, N)
273+ ex["ex7"]["va_reserve_perpetuelle"] = ex["ex7"]["reserve_equiv"] / r
274+ return ex
275+
276+
277+def _f(x): return f"{x:,.2f}".replace(",", " ").replace(".", ",")
278+
279+
280+if __name__ == "__main__":
281+ pd.set_option("display.width", 220); pd.set_option("display.max_columns", 30)
282+ res = calculer(); b = res["base"]
283+ print("=== Cas 3 — historique 2023-2025 ($)"); print(res["historique"].round(3).to_string())
284+ print("\n=== Cas 3 — dépenses normalisées (première année)"); print(res["detail"].round(2).to_string())
285+ print(f"\nRBE {_f(b['rbe'])} | DE hors réserve {_f(b['depenses_hors_reserve'])} | réserve {_f(b['reserve'])} | total {_f(b['depenses_totales'])} | ratio {b['ratio_depenses']*100:.2f} %")
286+ print(f"RNE {_f(b['rne'])} | par logement {_f(res['rne_par_logement'])} | par m² {_f(res['rne_par_m2'])} | RNE/RBE {res['rne_pct_rbe']*100:.1f} %")
287+ cl = res["classement"]
288+ print(f"Fixes {_f(cl['fixes'])} ({cl['part_fixes']*100:.1f} %) | variables {_f(cl['variables'])} ({cl['part_variables']*100:.1f} %) | réserve {_f(cl['reserve'])} ({cl['part_reserve']*100:.1f} %)")
289+ print(f"Dépenses par logement {_f(cl['total_par_logement'])} | par m² {_f(cl['total_par_m2'])}")
290+ print("Groupes (% du RBE) :"); print((cl["groupes_pct_rbe"] * 100).round(2).to_string())
291+ print(f"\nDépenses déclarées 2025 {_f(res['depenses_2025_declarees'])} ; écart de normalisation {_f(res['ecart_normalisation'])} ; RNE historique {_f(res['rne_historique_2025'])} ; V hist {_f(res['V_historique'])} vs V {_f(res['V'])}")
292+ print("\n=== Normalisation alternative (moyenne indexée vs dernière indexée)"); print(res["alternative"].round(2).to_string())
293+ print("\n=== Démonstration réserve / taux"); print({k: round(v, 6) for k, v in res["demo_reserve"].items()})
294+ print("\n=== Réserve par composantes (hypothèses pédagogiques)"); print(res["reserve_composantes"]["detail"].round(2).to_string())
295+ print({k: round(v, 2) for k, v in res["reserve_composantes"].items() if k != "detail"})
296+ print("\n=== CAPEX programmés → annuité équivalente"); print({k: (round(v, 2) if isinstance(v, float) else v) for k, v in res["equivalence_capex"].items()})
297+ print("\n=== Loyers au marché"); print({k: round(v, 4) for k, v in res["marche"].items()})
298+ print("\n=== Comparables : ratios"); print({k: round(v, 4) for k, v in res["comps_stats"].items()})
299+ s = res["sensibilites"]
300+ print("\n=== Sensibilité réserve"); print(s["reserve"].round(4).to_string())
301+ print("\n=== Sensibilité taxes"); print(s["taxe"].round(4).to_string())
302+ print("\n=== Sensibilité gestion"); print(s["gestion"].round(4).to_string())
303+ print(f"dV/dDE = {s['dV_dDE']:.3f} ; élasticité RNE/dépenses = {s['elasticite_rne_depenses']:.3f}")
304+ c2 = res["cas2"]
305+ print("\n=== Cas 2"); print(c2["detail"].round(2).to_string())
306+ print({k: (round(v, 4) if isinstance(v, float) else v) for k, v in c2["base"].items() if k != "detail"})
307+ print("classement :", {k: (round(v, 4) if isinstance(v, float) else None) for k, v in c2["classement"].items() if isinstance(v, float)})
308+ print("taxes Québec recalculées :", {k: round(v, 2) for k, v in c2["taxes_quebec_recalc"].items()})
309+ print(f"RNE/log {_f(c2['rne_par_logement'])} ; RNE/m² {_f(c2['rne_par_m2'])} ; réserve/log {_f(c2['reserve_par_logement'])}")
310+ print("\n=== Exercices")
311+ ex = exercices(res)
312+ for k, v in ex.items():
313+ print(k, {kk: (round(vv, 4) if isinstance(vv, float) else ("<df>" if isinstance(vv, pd.DataFrame) else vv)) for kk, vv in v.items()} if isinstance(v, dict) else v)
added analysis/python/cases/ch33_six_logements.py +331 −0
@@ -0,0 +1,331 @@
1+#!/usr/bin/env python3
2+"""Chapitre 33 — Immeubles de six logements et plus : tous les chiffres cités dans manuscript/chapters/ch33.tex
3+et solutions/ch33.tex (DONNÉES SYNTHÉTIQUES des cas 3 et 4, plus des données RÉELLES de la SCHL lues dans data/raw).
4+
5+Contenu :
6+ - schl_ile_montreal() : lecture des tableaux 1.3.1/1.3.2/1.3.4 (taille d'immeuble) et 1.2.1/1.2.2 (année de
7+ construction) du classeur SCHL « rmr-montreal-2025-fr.xlsx » (Enquête sur les logements locatifs, octobre 2025),
8+ bloc « Île de Montréal (zones 1-18) » — données réelles, citées avec attribution (conditions d'utilisation SCHL).
9+ - sommaire_cas4() : portefeuille du cas 4 par RMR (traite.cas4.sommaire) + statistiques de taux et de taille.
10+ - effet_taille() : effet de la taille sur le taux global dans le cas 3 (régression linéaire de traite.cas3) et dans
11+ le cas 4 (régression semi-log de traite.cas4.effets_regionaux), et taux médians par classe de taille.
12+ - unites_cas3() : analyse par logement, par m², par chambre et multiplicateurs pour le sujet du cas 3.
13+ - p45() : évaluation sommaire par capitalisation d'un immeuble de 36 logements du cas 4 (P45, Gatineau) avec
14+ plusieurs taux candidats (Montréal cas 3, Gatineau cas 4, régression cas 4) pour isoler l'effet de RMR et de taille.
15+ - sensibilite_p45() : grilles RMR × état et courbe taille → taux prédit et valeur.
16+ - exercices() : chiffres des solutions.
17+
18+Aucune formule financière n'est réécrite : traite.finance, traite.cas3, traite.cas4, traite.hedonique.
19+Usage (racine du dépôt) : PYTHONPATH=analysis/python python3 analysis/python/cases/ch33_six_logements.py
20+"""
21+from __future__ import annotations
22+import sys, pathlib
23+import numpy as np, pandas as pd
24+sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1]))
25+from traite import cas3, cas4, finance as fin, hedonique as h
26+
27+ROOT = pathlib.Path(__file__).resolve().parents[3]
28+SCHL_XLSX = ROOT / "data/raw/schl_rml_2025/rmr-montreal-2025-fr.xlsx"
29+
30+# ----------------------------------------------------------------------------- constantes citées (sources : notes-B, notes-E, ch. 47)
31+V_FINALE_CH47 = 2_060_000 # valeur marchande finale du cas 3 au ch. 47 (réconciliation 45/35/20) — vérifiée par test contre ch47_dossier
32+TAXE_QUEBEC_1A5 = 0.7464 / 100 # Ville de Québec 2026, résidentiel 1 à 5 logements, $ par 100 $ (R.V.Q. 3492)
33+TAXE_QUEBEC_6PLUS = 0.7697 / 100 # Ville de Québec 2026, résidentiel 6 logements et plus
34+TARIF_QUEBEC_EAU = 386.0 # $ par logement (aqueduc-égout) ; + 129 $ par chambre (non utilisé ici)
35+TARIF_QUEBEC_MATIERES = 195.0 # $ par logement (matières résiduelles)
36+IPCB_MTL_BASSE_HAUTEUR_T3_2026 = 0.092 # IPCB Montréal, immeuble < 5 étages, variation annuelle T3 2026 (StatCan 18-10-0289-01)
37+IPCB_MTL_TOUR_T3_2026 = 0.079 # idem, tour ≥ 5 étages
38+ID_36 = "P45" # immeuble de 36 logements retenu dans le cas 4 (Gatineau, 1954, état 2)
39+RMR_LISTE = ["Montréal", "Québec", "Gatineau", "Sherbrooke", "Trois-Rivières", "Saguenay"]
40+
41+
42+# ----------------------------------------------------------------------------- SCHL (données réelles)
43+
44+def _lire_bloc(ws, titre_bloc: str, n_groupes: int, largeur_groupe: int, labels_arret=("Tous les immeubles",)) -> pd.DataFrame:
45+ """Lit les lignes du bloc `titre_bloc` d'une feuille SCHL. Chaque ligne = [libellé, g1..gk] ; chaque groupe de
46+ `largeur_groupe` cellules = (oct. 2024, qualité, oct. 2025, qualité[, flèche]). Retourne, par ligne, les valeurs
47+ d'octobre 2025 des groupes 2 chambres (3e) et tous (5e), avec leur lettre de qualité."""
48+ rows = list(ws.iter_rows(values_only=True))
49+ out, dans_bloc = [], False
50+ for r in rows:
51+ lab = r[0]
52+ if isinstance(lab, str) and lab.startswith(titre_bloc):
53+ dans_bloc = True; continue
54+ if not dans_bloc or lab is None:
55+ continue
56+ def val(g, off):
57+ v = r[1 + g * largeur_groupe + off]
58+ if v is None or str(v).strip() in ("**", "-", ""):
59+ return np.nan
60+ return float(str(v).replace(" ", "").replace(" ", "").replace(",", "."))
61+ def qual(g, off):
62+ q = r[1 + g * largeur_groupe + off]
63+ return str(q).strip() if isinstance(q, str) else ""
64+ out.append({"categorie": str(lab).strip(), "deux_ch_2025": val(2, 2), "deux_ch_qual": qual(2, 3),
65+ "tous_2025": val(4, 2), "tous_qual": qual(4, 3), "tous_2024": val(4, 0)})
66+ if str(lab).strip() in labels_arret:
67+ break
68+ return pd.DataFrame(out)
69+
70+
71+def schl_ile_montreal() -> dict:
72+ """Tableaux SCHL (octobre 2025), île de Montréal : inoccupation, loyer moyen et rotation par taille d'immeuble ;
73+ inoccupation et loyer moyen par année de construction. Valeurs « tous les appartements » et « 2 chambres »."""
74+ import openpyxl
75+ wb = openpyxl.load_workbook(SCHL_XLSX, read_only=True, data_only=True)
76+ bloc = "Île de Montréal"
77+ taille_inocc = _lire_bloc(wb["Tableau 1.3.1"], bloc, 5, 5)
78+ taille_loyer = _lire_bloc(wb["Tableau 1.3.2"], bloc, 5, 4)
79+ taille_rot = _lire_bloc(wb["Tableau 1.3.4"], bloc, 5, 5)
80+ annee_inocc = _lire_bloc(wb["Tableau 1.2.1"], bloc, 5, 5)
81+ annee_loyer = _lire_bloc(wb["Tableau 1.2.2"], bloc, 5, 4)
82+ taille = taille_inocc[["categorie"]].copy()
83+ taille["inoccupation_tous"] = taille_inocc["tous_2025"].values
84+ taille["inoccupation_2024"] = taille_inocc["tous_2024"].values
85+ taille["loyer_tous"] = taille_loyer.set_index("categorie").loc[taille["categorie"], "tous_2025"].values
86+ taille["loyer_2ch"] = taille_loyer.set_index("categorie").loc[taille["categorie"], "deux_ch_2025"].values
87+ taille["rotation_tous"] = taille_rot.set_index("categorie").loc[taille["categorie"], "tous_2025"].values
88+ annee = annee_inocc[["categorie"]].copy()
89+ annee["inoccupation_tous"] = annee_inocc["tous_2025"].values
90+ annee["loyer_tous"] = annee_loyer.set_index("categorie").loc[annee["categorie"], "tous_2025"].values
91+ annee["loyer_2ch"] = annee_loyer.set_index("categorie").loc[annee["categorie"], "deux_ch_2025"].values
92+ annee = annee[annee["categorie"] != "Inconnue"].reset_index(drop=True)
93+ return {"taille": taille, "annee": annee}
94+
95+
96+# ----------------------------------------------------------------------------- cas 4 : sommaire et effets
97+
98+def sommaire_cas4() -> dict:
99+ d = cas4.charger(); pf = d["portefeuille"]
100+ s = cas4.sommaire(pf)
101+ g = s["par_rmr"].copy()
102+ stats = pf.groupby("rmr").agg(taux_median=("taux_global", "median"), taux_min=("taux_global", "min"), taux_max=("taux_global", "max"),
103+ logements_moyen=("nb_logements", "mean"), loyer_moyen=("loyer_mensuel_moyen", "mean"),
104+ inocc_marche=("taux_inoccupation_marche", "first"), age_moyen=("age", "mean"))
105+ g = g.join(stats)
106+ g["rne_par_logement"] = g["rne"] / g["logements"]
107+ g = g.loc[RMR_LISTE]
108+ pf = pf.assign(valeur_par_logement=pf["valeur_2026"] / pf["nb_logements"], rne_par_logement=pf["rne_2026"] / pf["nb_logements"],
109+ mrb=pf["valeur_2026"] / pf["revenu_brut_potentiel"], rpv=pf["dette"] / pf["valeur_2026"])
110+ return {"par_rmr": g, "total": s["total"], "portefeuille": pf, "series": d["series"]}
111+
112+
113+def effet_taille(pf: pd.DataFrame) -> dict:
114+ """Effet de la taille sur le taux global : (i) cas 3, pente linéaire par logement (traite.cas3.taux_extraits) ;
115+ (ii) cas 4, élasticité semi-log (traite.cas4.effets_regionaux) ; (iii) taux médians par classe de taille (cas 4)."""
116+ t3 = cas3.tout()
117+ reg3 = t3["taux"]["regression"]
118+ eff4 = cas4.effets_regionaux(pf)
119+ b_ln = float(eff4.loc["ln_logements", "coef"]); se_ln = float(eff4.loc["ln_logements", "se_hc1"])
120+ classes = pd.cut(pf["nb_logements"], bins=[5, 8, 16, 24, 48, 72], labels=["6–8", "12–16", "24", "36–48", "72"])
121+ par_classe = pf.groupby(classes, observed=True).agg(n=("id_immeuble", "count"), taux_median=("taux_global", "median"),
122+ valeur_par_logement=("valeur_2026", lambda v: float(v.sum()) / float(pf.loc[v.index, "nb_logements"].sum())))
123+ # effet, en points de base, de passer de 12 à 36 logements, toutes choses égales par ailleurs, à un taux de 5,5 % (cas 4) et à la médiane du cas 3
124+ r_ref4 = float(np.exp(eff4.loc["const", "coef"])) # Montréal, état 3, 12 logements, 50 ans, sans risque
125+ d_pb_cas4 = (np.exp(b_ln * np.log(36 / 12)) - 1) * r_ref4 * 1e4
126+ d_pb_cas3 = reg3["taille"] * (36 - 12) * 1e4
127+ return {"cas3_pente_pb_par_logement": reg3["taille"] * 1e4, "cas3_constante": reg3["constante"], "cas3_mediane": t3["taux"]["mediane"],
128+ "cas3_n": t3["taux"]["n"], "cas3_comparables": t3["taux"]["comparables"], "cas3_valeur_cap": t3["valeur_capitalisation"],
129+ "cas3_rne": t3["base"]["rne"], "cas3_base": t3["base"], "cas3_etat": t3["etat"], "cas3_sujet": t3["sujet"], "cas3_dcf": t3["dcf"],
130+ "cas4_coef_ln": b_ln, "cas4_se_ln": se_ln, "cas4_effet_doublement_pct": float(np.exp(b_ln * np.log(2)) - 1),
131+ "cas4_effets": eff4, "cas4_r_ref": r_ref4, "d_pb_12_36_cas4": float(d_pb_cas4), "d_pb_12_36_cas3": float(d_pb_cas3), "par_classe": par_classe}
132+
133+
134+# ----------------------------------------------------------------------------- unités d'analyse (cas 3)
135+
136+def unites_cas3(et: dict, valeur: float = V_FINALE_CH47) -> dict:
137+ etat, sujet, base = et["cas3_etat"], et["cas3_sujet"], et["cas3_base"]
138+ n = int(sujet["nb_logements"]); m2 = float(sujet["superficie_locative_m2"])
139+ chambres = int(etat["chambres"].sum())
140+ rbp = base["rbp_contrat"]; rne = base["rne"]
141+ return {"valeur": valeur, "n": n, "m2": m2, "chambres": chambres, "par_logement": valeur / n, "par_m2": valeur / m2, "par_chambre": valeur / chambres,
142+ "m2_par_logement": m2 / n, "chambres_par_logement": chambres / n, "mrb": fin.multiplicateur_revenu_brut(valeur, rbp),
143+ "mrb_marche": fin.multiplicateur_revenu_brut(valeur, base["rbp_marche"]), "taux_implicite": fin.taux_global_extrait(rne, valeur),
144+ "rne_par_logement": rne / n, "loyer_moyen": float(etat["loyer_mensuel_contrat"].mean()), "loyer_moyen_m2": float((etat["loyer_mensuel_contrat"].sum()) / m2),
145+ "valeur_cap": et["cas3_valeur_cap"], "valeur_cap_par_logement": et["cas3_valeur_cap"] / n}
146+
147+
148+# ----------------------------------------------------------------------------- P45 : 36 logements à Gatineau
149+
150+def _X_ligne(rmr: str, etat: int, n: int, age: float, inond: int = 0, sol: int = 0) -> np.ndarray:
151+ x = [1.0, etat - 3, np.log(n / 12), inond, sol, (age - 50) / 10]
152+ x += [1.0 if rmr == r else 0.0 for r in ["Québec", "Gatineau", "Sherbrooke", "Trois-Rivières", "Saguenay"]]
153+ return np.array(x)
154+
155+
156+def _regression_cas4(pf: pd.DataFrame) -> dict:
157+ """Même spécification que traite.cas4.effets_regionaux, mais on conserve les résidus pour le facteur de Duan."""
158+ X = pd.DataFrame({"const": 1.0, "etat": pf["etat_1a5"] - 3, "ln_logements": np.log(pf["nb_logements"] / 12), "inondable": pf["zone_inondable"],
159+ "sol": pf["contamination_sol"], "age": (pf["age"] - 50) / 10})
160+ for r in ["Québec", "Gatineau", "Sherbrooke", "Trois-Rivières", "Saguenay"]:
161+ X[f"rmr_{r}"] = (pf["rmr"] == r).astype(float)
162+ res = h.mco(X.values, np.log(pf["taux_global"].values))
163+ res["duan"] = float(np.mean(np.exp(res["residus"])))
164+ res["colonnes"] = list(X.columns)
165+ return res
166+
167+
168+def taux_predit(reg: dict, rmr: str, etat: int, n: int, age: float, inond: int = 0, sol: int = 0, duan: bool = True) -> float:
169+ x = _X_ligne(rmr, etat, n, age, inond, sol)
170+ return float(np.exp(x @ reg["beta"]) * (reg["duan"] if duan else 1.0))
171+
172+
173+def p45(som: dict, et: dict, amortissement_mois: int = 300) -> dict:
174+ pf = som["portefeuille"]; row = pf.set_index("id_immeuble").loc[ID_36]
175+ reg = _regression_cas4(pf)
176+ rne = float(row["rne_2026"]); n = int(row["nb_logements"]); rmr = str(row["rmr"]); etat = int(row["etat_1a5"]); age = float(row["age"])
177+ g = som["par_rmr"]
178+ taux = {
179+ "montreal_cas3_mediane": et["cas3_mediane"], # erreur : taux d'un autre marché
180+ "gatineau_pondere": float(g.loc[rmr, "taux_pondere"]), # RNE/valeur agrégés, Gatineau (cas 4)
181+ "gatineau_median": float(g.loc[rmr, "taux_median"]), # médiane des 8 immeubles de Gatineau
182+ "regression_sujet": taux_predit(reg, rmr, etat, n, age), # régression semi-log, caractéristiques de P45
183+ "regression_si_montreal": taux_predit(reg, "Montréal", etat, n, age), # même immeuble à Montréal (effet RMR)
184+ "regression_si_12_logements": taux_predit(reg, rmr, etat, 12, age), # même immeuble à 12 logements (effet taille)
185+ "regression_si_etat_3": taux_predit(reg, rmr, 3, n, age), # même immeuble en état moyen
186+ "fichier": float(row["taux_global"]), # taux inscrit au dossier synthétique
187+ }
188+ valeurs = {k: fin.capitalisation_directe(rne, R) for k, R in taux.items()}
189+ par_log = {k: v / n for k, v in valeurs.items()}
190+ hyp = fin.Hypotheque(float(row["dette"]), float(row["taux_dette"]), amortissement_mois)
191+ fin_ = {"dette": float(row["dette"]), "taux_dette": float(row["taux_dette"]), "echeance": int(row["echeance_dette"]), "service_dette": hyp.service_dette_annuel,
192+ "rcd": fin.rcd(rne, hyp.service_dette_annuel), "rpv_fichier": float(row["dette"]) / float(row["valeur_2026"]),
193+ "rpv_regression": float(row["dette"]) / valeurs["regression_sujet"], "constante": hyp.constante_hypothecaire}
194+ # décomposition de l'écart Montréal cas 3 → régression sujet (en points de base, cumul multiplicatif)
195+ r_mtl12 = taux_predit(reg, "Montréal", etat, 12, age)
196+ decomposition = {"regression_montreal_12_log_etat2_age72": r_mtl12,
197+ "effet_taille_pb": (taux_predit(reg, "Montréal", etat, n, age) - r_mtl12) * 1e4,
198+ "effet_rmr_pb": (taux["regression_sujet"] - taux_predit(reg, "Montréal", etat, n, age)) * 1e4}
199+ return {"id": ID_36, "rmr": rmr, "n": n, "annee": int(row["annee_construction"]), "age": age, "etat": etat, "loyer_moyen": float(row["loyer_mensuel_moyen"]),
200+ "rbp": float(row["revenu_brut_potentiel"]), "ratio_depenses": float(row["ratio_depenses"]), "rne": rne, "rne_par_logement": rne / n,
201+ "inocc_marche": float(row["taux_inoccupation_marche"]), "valeur_fichier": float(row["valeur_2026"]), "valeur_fichier_par_logement": float(row["valeur_2026"]) / n,
202+ "mrb_fichier": float(row["valeur_2026"]) / float(row["revenu_brut_potentiel"]), "taux": taux, "valeurs": valeurs, "par_logement": par_log,
203+ "regression": reg, "financement": fin_, "decomposition": decomposition,
204+ "ecart_montreal_vs_sujet": valeurs["montreal_cas3_mediane"] / valeurs["regression_sujet"] - 1,
205+ "gatineau_valeur_par_logement": float(g.loc[rmr, "valeur_par_logement"]), "montreal_valeur_par_logement": float(g.loc["Montréal", "valeur_par_logement"])}
206+
207+
208+# ----------------------------------------------------------------------------- sensibilité
209+
210+def sensibilite_p45(p: dict) -> dict:
211+ reg, n, age, rne = p["regression"], p["n"], p["age"], p["rne"]
212+ grille = pd.DataFrame({r: [taux_predit(reg, r, e, n, age) for e in range(1, 6)] for r in RMR_LISTE}, index=[f"état {e}" for e in range(1, 6)])
213+ grille_valeur_log = grille.map(lambda R: rne / R / n)
214+ tailles = [6, 12, 24, 36, 48, 72]
215+ courbe = pd.DataFrame({"n": tailles, "taux_gatineau": [taux_predit(reg, "Gatineau", p["etat"], k, age) for k in tailles],
216+ "taux_montreal": [taux_predit(reg, "Montréal", p["etat"], k, age) for k in tailles]})
217+ courbe["valeur_par_logement_gatineau"] = rne / n / courbe["taux_gatineau"] # RNE par logement constant, taille variable
218+ R0 = p["taux"]["regression_sujet"]
219+ pb = pd.DataFrame({"delta_pb": [-50, -25, 0, 25, 50]})
220+ pb["taux"] = R0 + pb["delta_pb"] / 1e4; pb["valeur"] = rne / pb["taux"]; pb["variation_pct"] = pb["valeur"] / (rne / R0) - 1
221+ # élasticité exacte : dV/V = -dR/R
222+ return {"grille_taux": grille, "grille_valeur_par_logement": grille_valeur_log, "courbe_taille": courbe, "taux_pb": pb,
223+ "elasticite": -1.0, "variation_25pb_pct": float(R0 / (R0 + 0.0025) - 1)}
224+
225+
226+# ----------------------------------------------------------------------------- exercices
227+
228+def exercices(som: dict, et: dict, u: dict, p: dict) -> dict:
229+ ex = {}
230+ # ex1 — unités de comparaison : un 16 logements fictif (données de l'énoncé) évalué par les trois unités du cas 3
231+ n16, m2_16, ch16 = 16, 1040.0, 26
232+ ex["ex1"] = {"par_logement": u["par_logement"] * n16, "par_m2": u["par_m2"] * m2_16, "par_chambre": u["par_chambre"] * ch16,
233+ "m2_par_logement_sujet": u["m2_par_logement"], "m2_par_logement_16": m2_16 / n16, "chambres_par_logement_16": ch16 / n16}
234+ ex["ex1"]["ecart_max_pct"] = max(ex["ex1"]["par_logement"], ex["ex1"]["par_m2"], ex["ex1"]["par_chambre"]) / min(ex["ex1"]["par_logement"], ex["ex1"]["par_m2"], ex["ex1"]["par_chambre"]) - 1
235+ # ex2 — taxes de la Ville de Québec : 5 logements (1-5) vs 6 logements (6+) pour une même valeur au rôle
236+ role = 1_800_000
237+ taxe5 = role * TAXE_QUEBEC_1A5 + 5 * (TARIF_QUEBEC_EAU + TARIF_QUEBEC_MATIERES)
238+ taxe6 = role * TAXE_QUEBEC_6PLUS + 6 * (TARIF_QUEBEC_EAU + TARIF_QUEBEC_MATIERES)
239+ ex["ex2"] = {"role": role, "taxe_5": taxe5, "taxe_6": taxe6, "ecart_taux_seul": role * (TAXE_QUEBEC_6PLUS - TAXE_QUEBEC_1A5),
240+ "ecart_total": taxe6 - taxe5, "effet_valeur_a_5pct": role * (TAXE_QUEBEC_6PLUS - TAXE_QUEBEC_1A5) / 0.055}
241+ # ex3 — effet de taille (cas 3) : 36 logements au lieu de 12
242+ R12 = et["cas3_constante"]; R36 = et["cas3_constante"] + (et["cas3_pente_pb_par_logement"] / 1e4) * (36 - 12)
243+ rne_log = et["cas3_rne"] / 12
244+ ex["ex3"] = {"R12": R12, "R36": R36, "ecart_pb": (R36 - R12) * 1e4, "valeur_par_logement_12": rne_log / R12, "valeur_par_logement_36": rne_log / R36,
245+ "prime_pct": rne_log / R36 / (rne_log / R12) - 1, "cas4_doublement_pct": et["cas4_effet_doublement_pct"]}
246+ # ex4 — erreur de marché : P45 au taux montréalais
247+ ex["ex4"] = {"valeur_montreal": p["valeurs"]["montreal_cas3_mediane"], "valeur_regression": p["valeurs"]["regression_sujet"],
248+ "ecart": p["valeurs"]["montreal_cas3_mediane"] - p["valeurs"]["regression_sujet"], "ecart_pct": p["ecart_montreal_vs_sujet"],
249+ "valeur_gatineau_pondere": p["valeurs"]["gatineau_pondere"], "effet_rmr_pb": p["decomposition"]["effet_rmr_pb"], "effet_taille_pb": p["decomposition"]["effet_taille_pb"]}
250+ # ex5 — diligence : 4 baux surévalués de 120 $/mois dans l'état locatif de P45 ; vacance 3,8 %, gestion 4 % du RBE, R = régression
251+ delta_loyer = 4 * 120 * 12
252+ R = p["taux"]["regression_sujet"]; v = p["inocc_marche"]
253+ delta_rne = delta_loyer * (1 - v) * (1 - 0.04)
254+ ex["ex5"] = {"delta_rbp": delta_loyer, "delta_rne": delta_rne, "delta_valeur": delta_rne / R, "delta_valeur_pct": delta_rne / R / p["valeurs"]["regression_sujet"],
255+ "R": R, "v": v}
256+ # ex6 — régression : 24 logements, état 3, 45 ans, dans chaque RMR ; IC à 95 % approximatif sur l'effet RMR (HC1)
257+ reg = p["regression"]; cols = reg["colonnes"]
258+ ex6 = {}
259+ for r in RMR_LISTE:
260+ R = taux_predit(reg, r, 3, 24, 45)
261+ if r == "Montréal":
262+ lo = hi = None
263+ else:
264+ j = cols.index(f"rmr_{r}"); b, s = reg["beta"][j], reg["se_hc1"][j]
265+ lo, hi = np.exp(b - 1.96 * s) - 1, np.exp(b + 1.96 * s) - 1
266+ ex6[r] = {"taux": R, "effet_pct": (np.exp(reg["beta"][cols.index(f"rmr_{r}")]) - 1) if r != "Montréal" else 0.0, "ic_lo": lo, "ic_hi": hi}
267+ ex["ex6"] = {"par_rmr": ex6, "r2": reg["r2"], "n": reg["n"], "duan": reg["duan"]}
268+ # ex7 — immeuble neuf : coût indexé vs revenu (données de l'énoncé : coût 2025 hors terrain 6 400 000 $, terrain 1 100 000 $, 24 logements,
269+ # loyer 1 975 $/mois, inoccupation 5,6 % (SCHL, île, immeubles 2015+), dépenses 32 % du RBE, taux neuf CBRE Montréal 4,50-4,75 %)
270+ cout_2025, terrain, n_neuf, loyer_neuf, v_neuf, ratio_neuf = 6_400_000, 1_100_000, 24, 1975, 0.056, 0.32
271+ cout_2026 = cout_2025 * (1 + IPCB_MTL_BASSE_HAUTEUR_T3_2026)
272+ rbp = n_neuf * loyer_neuf * 12; rbe = fin.revenu_brut_effectif(rbp, v_neuf); rne_neuf = fin.rne(rbe, ratio_neuf * rbe)
273+ ex["ex7"] = {"cout_2026": cout_2026, "cout_total": cout_2026 + terrain, "cout_par_logement": (cout_2026 + terrain) / n_neuf, "rbp": rbp, "rbe": rbe, "rne": rne_neuf,
274+ "V_450": fin.capitalisation_directe(rne_neuf, 0.045), "V_475": fin.capitalisation_directe(rne_neuf, 0.0475),
275+ "taux_implicite_cout": rne_neuf / (cout_2026 + terrain), "valeur_assurable_hors_terrain": cout_2026}
276+ return ex
277+
278+
279+# ----------------------------------------------------------------------------- assemblage et impression
280+
281+def calculer() -> dict:
282+ som = sommaire_cas4(); et = effet_taille(som["portefeuille"]); u = unites_cas3(et); p = p45(som, et); s = sensibilite_p45(p)
283+ schl = schl_ile_montreal()
284+ return {"sommaire": som, "effet_taille": et, "unites": u, "p45": p, "sensibilite": s, "schl": schl, "exercices": exercices(som, et, u, p)}
285+
286+
287+def _d(x): return f"{x:,.0f}".replace(",", " ") + " $"
288+def _p(x, d=2): return f"{100*x:.{d}f}".replace(".", ",") + " %"
289+
290+
291+def imprimer(R: dict) -> None:
292+ pd.set_option("display.width", 200); pd.set_option("display.max_columns", 20)
293+ print("=== SCHL, île de Montréal, octobre 2025 (données réelles) ===")
294+ print(R["schl"]["taille"].to_string(index=False)); print(R["schl"]["annee"].to_string(index=False))
295+ g = R["sommaire"]["par_rmr"]; t = R["sommaire"]["total"]
296+ print("\n=== Cas 4 : sommaire par RMR ===")
297+ print(g[["n", "logements", "valeur", "rne", "taux_pondere", "taux_median", "taux_min", "taux_max", "rpv", "valeur_par_logement", "rne_par_logement", "logements_moyen", "loyer_moyen"]].to_string())
298+ print(f"Total : {t['n']} immeubles, {t['logements']} logements, valeur {_d(t['valeur'])}, RNE {_d(t['rne'])}, taux pondéré {_p(t['taux_pondere'])}, RPV {_p(t['rpv'], 1)}, valeur/logement {_d(t['valeur']/t['logements'])}")
299+ et = R["effet_taille"]
300+ print("\n=== Effet de taille ===")
301+ print(f"Cas 3 : pente {et['cas3_pente_pb_par_logement']:.2f} pb/logement ; constante {_p(et['cas3_constante'], 3)} ; médiane {_p(et['cas3_mediane'], 3)} ; 12→36 : {et['d_pb_12_36_cas3']:.1f} pb")
302+ print(f"Cas 4 : coef ln(n/12) = {et['cas4_coef_ln']:.4f} (ET HC1 {et['cas4_se_ln']:.4f}) ; doublement → {_p(et['cas4_effet_doublement_pct'])} du taux ; 12→36 à R réf. {_p(et['cas4_r_ref'], 2)} : {et['d_pb_12_36_cas4']:.1f} pb")
303+ print(et["par_classe"].to_string()); print(et["cas4_effets"].to_string())
304+ u = R["unites"]
305+ print("\n=== Unités (cas 3, valeur finale ch. 47) ===")
306+ print(f"V = {_d(u['valeur'])} ; {u['n']} log., {u['m2']:.0f} m², {u['chambres']} chambres → {_d(u['par_logement'])}/log., {_d(u['par_m2'])}/m², {_d(u['par_chambre'])}/chambre ; "
307+ f"{u['m2_par_logement']:.1f} m²/log. ; {u['chambres_par_logement']:.2f} ch./log. ; MRB contrat {u['mrb']:.2f}, marché {u['mrb_marche']:.2f} ; taux implicite {_p(u['taux_implicite'])} ; "
308+ f"RNE/log. {_d(u['rne_par_logement'])} ; loyer moyen {_d(u['loyer_moyen'])}/mois ; loyer {u['loyer_moyen_m2']:.2f} $/m²/mois ; V_cap {_d(u['valeur_cap'])} ({_d(u['valeur_cap_par_logement'])}/log.)")
309+ p = R["p45"]
310+ print(f"\n=== {p['id']} : {p['n']} logements, {p['rmr']}, {p['annee']} ({p['age']:.0f} ans), état {p['etat']} ===")
311+ print(f"Loyer moyen {_d(p['loyer_moyen'])}/mois ; RBP {_d(p['rbp'])} ; ratio dépenses {_p(p['ratio_depenses'], 1)} ; inoccupation de marché {_p(p['inocc_marche'], 1)} ; RNE {_d(p['rne'])} ({_d(p['rne_par_logement'])}/log.)")
312+ print(f"Valeur au dossier {_d(p['valeur_fichier'])} ({_d(p['valeur_fichier_par_logement'])}/log., MRB {p['mrb_fichier']:.2f}, taux {_p(p['taux']['fichier'])})")
313+ for k in p["taux"]:
314+ print(f" {k:30s} R = {_p(p['taux'][k], 3)} V = {_d(p['valeurs'][k])} ({_d(p['par_logement'][k])}/log.)")
315+ d = p["decomposition"]
316+ print(f"Décomposition : Montréal 12 log. état 2 âge 72 → {_p(d['regression_montreal_12_log_etat2_age72'], 3)} ; effet taille {d['effet_taille_pb']:+.1f} pb ; effet RMR {d['effet_rmr_pb']:+.1f} pb")
317+ print(f"Écart Montréal cas 3 vs régression : {_p(p['ecart_montreal_vs_sujet'], 1)} ; valeur/log. Gatineau cas 4 {_d(p['gatineau_valeur_par_logement'])}, Montréal {_d(p['montreal_valeur_par_logement'])}")
318+ f = p["financement"]
319+ print(f"Dette {_d(f['dette'])} à {_p(f['taux_dette'])} (échéance {f['echeance']}) ; service (300 mois, hyp.) {_d(f['service_dette'])} ; RCD {f['rcd']:.2f} ; RPV dossier {_p(f['rpv_fichier'], 1)} ; RPV sur V régression {_p(f['rpv_regression'], 1)} ; constante {_p(f['constante'])}")
320+ reg = p["regression"]; print(f"Régression cas 4 : n = {reg['n']}, R² = {reg['r2']:.3f}, facteur de Duan {reg['duan']:.4f}")
321+ s = R["sensibilite"]
322+ print("\n=== Sensibilité P45 ===")
323+ print((s["grille_taux"] * 100).round(2).to_string()); print(s["grille_valeur_par_logement"].round(0).to_string()); print(s["courbe_taille"].to_string(index=False)); print(s["taux_pb"].to_string(index=False))
324+ print(f"Variation de la valeur pour +25 pb : {_p(s['variation_25pb_pct'])}")
325+ print("\n=== Exercices ===")
326+ for k, v in R["exercices"].items():
327+ print(k, {kk: (round(vv, 4) if isinstance(vv, float) else vv) for kk, vv in v.items()} if k != "ex6" else {r: {kk: (round(vv, 4) if isinstance(vv, float) else vv) for kk, vv in d.items()} for r, d in v["par_rmr"].items()} | {"r2": round(v["r2"], 3), "duan": round(v["duan"], 4)})
328+
329+
330+if __name__ == "__main__":
331+ imprimer(calculer())
modified analysis/python/figures/ch25_figures.py +17 −16
@@ -52,7 +52,8 @@ def fig_carte(S):
52 th = np.linspace(0, 2 * np.pi, 200) 52 th = np.linspace(0, 2 * np.pi, 200)
53 ax.plot(suj["lon"] + 4 / 78 * np.cos(th), suj["lat"] + 4 / 111 * np.sin(th), color=palette.cat[3], lw=0.8, ls=":") 53 ax.plot(suj["lon"] + 4 / 78 * np.cos(th), suj["lat"] + 4 / 111 * np.sin(th), color=palette.cat[3], lw=0.8, ls=":")
54 for sct, (la, lo) in cas.CENTROIDES.items(): 54 for sct, (la, lo) in cas.CENTROIDES.items():
55− ax.text(lo, la + 0.030, sct, ha="center", fontsize=8, color=palette.ink, fontweight="semibold") 55+ dy = -0.036 if sct == "Masson-Angers" else 0.030
56+ ax.text(lo, la + dy, sct, ha="center", fontsize=8, color=palette.ink, fontweight="semibold")
56 ax.set_xlabel("Longitude (coordonnées fictives)"); ax.set_ylabel("Latitude (coordonnées fictives)") 57 ax.set_xlabel("Longitude (coordonnées fictives)"); ax.set_ylabel("Latitude (coordonnées fictives)")
57 ax.set_aspect(111 / 78); ax.legend(loc="lower right", fontsize=7.5) 58 ax.set_aspect(111 / 78); ax.legend(loc="lower right", fontsize=7.5)
58 ax.set_xlim(-75.92, -75.33); ax.set_ylim(45.34, 45.64) 59 ax.set_xlim(-75.92, -75.33); ax.set_ylim(45.34, 45.64)
@@ -61,7 +62,7 @@ def fig_carte(S):
61 62
62 def fig_lotissement(S): 63 def fig_lotissement(S):
63 L = S["lotissement"]; det = L["detail"]; p = L["params"]; m = S["comparaison"]["modele"] 64 L = S["lotissement"]; det = L["detail"]; p = L["params"]; m = S["comparaison"]["modele"]
64− fig, (a1, a2) = plt.subplots(1, 2, figsize=(LARGEUR, 3.4), gridspec_kw={"width_ratios": [1.1, 1]}) 65+ fig, (a1, a2) = plt.subplots(1, 2, figsize=(LARGEUR, 3.6), gridspec_kw={"width_ratios": [1.0, 1.0], "wspace": 0.95})
65 yrs = det["annee"].values 66 yrs = det["annee"].values
66 couts = -(det["infrastructure"] + det["honoraires"] + det["taxes"] + det["frais_vente"] + det["profit"]).values 67 couts = -(det["infrastructure"] + det["honoraires"] + det["taxes"] + det["frais_vente"] + det["profit"]).values
67 a1.bar(yrs, det["revenus"].values, color=palette.cat[0], width=0.6, label="Produit des ventes de lots") 68 a1.bar(yrs, det["revenus"].values, color=palette.cat[0], width=0.6, label="Produit des ventes de lots")
@@ -69,24 +70,24 @@ def fig_lotissement(S):
69 r = p["taux"]; cum = np.cumsum([f / (1 + r) ** t for t, f in zip(yrs, L["flux"])]) 70 r = p["taux"]; cum = np.cumsum([f / (1 + r) ** t for t, f in zip(yrs, L["flux"])])
70 a1.plot(yrs, cum, color=palette.cat[3], marker="o", lw=1.6, label="Valeur actualisée cumulée (10 %)") 71 a1.plot(yrs, cum, color=palette.cat[3], marker="o", lw=1.6, label="Valeur actualisée cumulée (10 %)")
71 for t, v in zip(yrs, cum): 72 for t, v in zip(yrs, cum):
72− a1.annotate(fmt_dollars(v), (t, v), xytext=(0, 6), textcoords="offset points", ha="center", fontsize=7, color=palette.ink) 73+ a1.annotate(fmt_milliers(v / 1000), (t, v), xytext=(0, 6), textcoords="offset points", ha="center", fontsize=7, color=palette.ink)
73 a1.axhline(0, color=palette.ink2, lw=0.6) 74 a1.axhline(0, color=palette.ink2, lw=0.6)
74− a1.set_xticks(yrs); a1.set_xticklabels([f"Année {t}" for t in yrs]); a1.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p_: fmt_milliers(x / 1000))) 75+ a1.set_xticks(yrs); a1.set_xticklabels([str(t) for t in yrs]); a1.set_xlabel("Année (flux de fin d'année)"); a1.set_ylim(-1_250_000, 1_450_000); a1.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p_: fmt_milliers(x / 1000)))
75 a1.set_ylabel("Milliers de dollars"); a1.set_title("(a) Flux du lotissement de 12 lots", loc="left", fontsize=9) 76 a1.set_ylabel("Milliers de dollars"); a1.set_title("(a) Flux du lotissement de 12 lots", loc="left", fontsize=9)
76− a1.legend(fontsize=7, loc="upper left") 77+ a1.legend(fontsize=6.5, loc="lower left", handlelength=1.2)
77 sl = cas.sensibilite_lotissement(m); base = sl["base"] 78 sl = cas.sensibilite_lotissement(m); base = sl["base"]
78− items = [("Absorption : 2 à 6 lots/an", sl["absorption"][2], sl["absorption"][6]), 79+ items = [("Absorption\n2 → 6 lots/an", sl["absorption"][2], sl["absorption"][6]),
79− ("Prix des lots : −10 % / +10 %", sl["prix"][-0.10], sl["prix"][0.10]), 80+ ("Prix des lots\n−10 % → +10 %", sl["prix"][-0.10], sl["prix"][0.10]),
80− ("Infrastructures : 80 000 / 50 000 $ par lot", sl["infrastructure"][80_000], sl["infrastructure"][50_000]), 81+ ("Infrastructures\n80 000 → 50 000 $/lot", sl["infrastructure"][80_000], sl["infrastructure"][50_000]),
81− ("Profit du promoteur : 20 % / 10 %", sl["profit"][0.20], sl["profit"][0.10]), 82+ ("Profit du promoteur\n20 % → 10 %", sl["profit"][0.20], sl["profit"][0.10]),
82− ("Taux d'actualisation : 15 % / 8 %", sl["taux"][0.15], sl["taux"][0.08])] 83+ ("Taux d'actualisation\n15 % → 8 %", sl["taux"][0.15], sl["taux"][0.08])]
83 items.sort(key=lambda x: abs(x[2] - x[1]), reverse=True) 84 items.sort(key=lambda x: abs(x[2] - x[1]), reverse=True)
84 for k, (lab, lo, hi) in enumerate(items): 85 for k, (lab, lo, hi) in enumerate(items):
85 a2.barh(k, lo - base, left=base, color=palette.cat[1], height=0.55) 86 a2.barh(k, lo - base, left=base, color=palette.cat[1], height=0.55)
86 a2.barh(k, hi - base, left=base, color=palette.cat[0], height=0.55) 87 a2.barh(k, hi - base, left=base, color=palette.cat[0], height=0.55)
87 a2.text(min(lo, hi) - 8000, k, fmt_milliers((min(lo, hi)) / 1000), va="center", ha="right", fontsize=7, color=palette.ink) 88 a2.text(min(lo, hi) - 8000, k, fmt_milliers((min(lo, hi)) / 1000), va="center", ha="right", fontsize=7, color=palette.ink)
88 a2.text(max(lo, hi) + 8000, k, fmt_milliers((max(lo, hi)) / 1000), va="center", ha="left", fontsize=7, color=palette.ink) 89 a2.text(max(lo, hi) + 8000, k, fmt_milliers((max(lo, hi)) / 1000), va="center", ha="left", fontsize=7, color=palette.ink)
89− a2.set_yticks(range(len(items))); a2.set_yticklabels([i[0] for i in items], fontsize=7.5) 90+ a2.set_yticks(range(len(items))); a2.set_yticklabels([i[0] for i in items], fontsize=7)
90 a2.axvline(base, color=palette.ink, lw=0.8) 91 a2.axvline(base, color=palette.ink, lw=0.8)
91 a2.set_xlim(base - 420_000, base + 420_000); a2.xaxis.set_major_formatter(plt.FuncFormatter(lambda x, p_: fmt_milliers(x / 1000))) 92 a2.set_xlim(base - 420_000, base + 420_000); a2.xaxis.set_major_formatter(plt.FuncFormatter(lambda x, p_: fmt_milliers(x / 1000)))
92 a2.set_xlabel("Valeur du site brut (milliers de dollars)"); a2.set_title(f"(b) Sensibilité autour de {fmt_dollars(base)}", loc="left", fontsize=9) 93 a2.set_xlabel("Valeur du site brut (milliers de dollars)"); a2.set_title(f"(b) Sensibilité autour de {fmt_dollars(base)}", loc="left", fontsize=9)
@@ -101,17 +102,17 @@ def fig_residuelle(S):
101 for d, col, ls in [(0.44, palette.cat[1], "--"), (0.52, palette.cat[0], "-"), (0.60, palette.cat[2], "-.")]: 102 for d, col, ls in [(0.44, palette.cat[1], "--"), (0.52, palette.cat[0], "-"), (0.60, palette.cat[2], "-.")]:
102 VL = [cas.residuelle_cas3({"taux_depreciation": d, "R_L": r}, t3)["V_L"] for r in RL] 103 VL = [cas.residuelle_cas3({"taux_depreciation": d, "R_L": r}, t3)["V_L"] for r in RL]
103 CB = cas.residuelle_cas3({"taux_depreciation": d}, t3)["C_B"] 104 CB = cas.residuelle_cas3({"taux_depreciation": d}, t3)["C_B"]
104− ax.plot(RL * 100, VL, color=col, ls=ls, lw=1.6, label=f"Dépréciation {d*100:.0f} % — $C_B$ = {fmt_dollars(CB)}") 105+ ax.plot(RL * 100, VL, color=col, ls=ls, lw=1.6, label=f"Dépréciation {d*100:.0f} % : bâtiment déprécié {fmt_dollars(CB)}")
105 ax.axhline(0, color=palette.ink2, lw=0.6) 106 ax.axhline(0, color=palette.ink2, lw=0.6)
106 ax.scatter([rs["R_L"] * 100], [rs["V_L"]], s=60, color=palette.cat[3], zorder=5, edgecolor=palette.ink, linewidths=0.5) 107 ax.scatter([rs["R_L"] * 100], [rs["V_L"]], s=60, color=palette.cat[3], zorder=5, edgecolor=palette.ink, linewidths=0.5)
107− ax.annotate(f"Cas de base : $R_L$ = 3,75 %, n = 40 ans\n$V_L$ = {fmt_dollars(rs['V_L'])}", (rs["R_L"] * 100, rs["V_L"]), xytext=(4.2, 900_000), fontsize=8, 108+ ax.annotate(f"Cas de base : taux du terrain 3,75 %, 40 ans restants\nvaleur résiduelle {fmt_dollars(rs['V_L'])}", (rs["R_L"] * 100, rs["V_L"]), xytext=(4.05, 1_000_000), fontsize=8,
108 arrowprops=dict(arrowstyle="-", color=palette.ink2, lw=0.6)) 109 arrowprops=dict(arrowstyle="-", color=palette.ink2, lw=0.6))
109 ext = rs["V_cap"] - rs["C_B"] 110 ext = rs["V_cap"] - rs["C_B"]
110 ax.axhline(ext, color=palette.ink2, lw=0.8, ls=":") 111 ax.axhline(ext, color=palette.ink2, lw=0.8, ls=":")
111− ax.text(3.02, ext + 25_000, f"Par extraction sur la capitalisation directe : $V$ − $C_B$ = {fmt_dollars(ext)}", fontsize=7.5, color=palette.ink2) 112+ ax.text(3.02, ext + 25_000, f"Extraction sur la valeur capitalisée (V − bâtiment déprécié) : {fmt_dollars(ext)}", fontsize=7.5, color=palette.ink2)
112− ax.set_xlabel("Taux de rendement du terrain $R_L$ (%)"); ax.set_ylabel("Valeur résiduelle du terrain $V_L$ ($)") 113+ ax.set_xlabel("Taux de rendement du terrain $R_L$ (%)"); ax.set_ylabel("Valeur résiduelle du terrain (dollars)")
113 ax.yaxis.set_major_formatter(plt.FuncFormatter(fmt_dollars)); ax.set_ylim(-300_000, 1_300_000) 114 ax.yaxis.set_major_formatter(plt.FuncFormatter(fmt_dollars)); ax.set_ylim(-300_000, 1_300_000)
114− ax.legend(loc="upper right", fontsize=7.5) 115+ ax.legend(loc="lower left", fontsize=7.5)
115 savefig(fig, "ch25/ch25-fig-residuelle-sensibilite.pdf") 116 savefig(fig, "ch25/ch25-fig-residuelle-sensibilite.pdf")
116 117
117 118
modified analysis/python/figures/ch26_figures.py +5 −6
@@ -40,13 +40,13 @@ def fig_anciennete(res):
40 fig, (a1, a2) = plt.subplots(1, 2, figsize=(LARGEUR, 3.1), gridspec_kw={"width_ratios": [1.15, 1]}) 40 fig, (a1, a2) = plt.subplots(1, 2, figsize=(LARGEUR, 3.1), gridspec_kw={"width_ratios": [1.15, 1]})
41 # (a) distribution de l'ancienneté (part des logements) et courbes de survie (1 − ρ)^t — même échelle de proportions, pas de double axe 41 # (a) distribution de l'ancienneté (part des logements) et courbes de survie (1 − ρ)^t — même échelle de proportions, pas de double axe
42 anc = t["anciennete"].values; bins = np.arange(0, 21, 2) 42 anc = t["anciennete"].values; bins = np.arange(0, 21, 2)
43− a1.hist(anc, bins=bins, weights=np.full(len(anc), 1 / len(anc)), color=palette.cat[0], alpha=0.85, edgecolor=palette.surface, zorder=3, label="part des 12 logements (ancienneté observée)") 43+ a1.hist(anc, bins=bins, weights=np.full(len(anc), 1 / len(anc)), color=palette.cat[0], alpha=0.85, edgecolor=palette.surface, zorder=3, label="part des 12 logements")
44 tt = np.linspace(0, 20, 200) 44 tt = np.linspace(0, 20, 200)
45− for rho, c, ls, lab in ((0.085, palette.cat[2], "-", "survie $(1-\\rho)^t$, ρ = 8,5 % (SCHL, 6–19 log., Montréal 2025)"), (0.094, palette.cat[3], "--", "survie, ρ = 9,4 % (SCHL, RMR Montréal 2025)"), (0.15, palette.cat[1], ":", "survie, ρ = 15 % (hypothèse du cas 3)")): 45+ for rho, c, ls, lab in ((0.085, palette.cat[2], "-", "survie $(1-\\rho)^t$, ρ = 8,5 % (SCHL, 6–19 log.)"), (0.094, palette.cat[3], "--", "survie, ρ = 9,4 % (SCHL, RMR Montréal)"), (0.15, palette.cat[1], ":", "survie, ρ = 15 % (cas 3)")):
46 a1.plot(tt, survie(rho, tt), color=c, linestyle=ls, linewidth=1.4, label=lab) 46 a1.plot(tt, survie(rho, tt), color=c, linestyle=ls, linewidth=1.4, label=lab)
47 a1.set_xlabel("Ancienneté du locataire (années)"); a1.set_ylabel("Proportion"); a1.set_ylim(0, 1.05); a1.set_xlim(0, 20) 47 a1.set_xlabel("Ancienneté du locataire (années)"); a1.set_ylabel("Proportion"); a1.set_ylim(0, 1.05); a1.set_xlim(0, 20)
48 a1.set_title("(a) Ancienneté observée et survie théorique", loc="left") 48 a1.set_title("(a) Ancienneté observée et survie théorique", loc="left")
49− a1.legend(loc="upper center", bbox_to_anchor=(0.5, -0.24), fontsize=6.6, ncol=1) 49+ a1.legend(loc="upper left", bbox_to_anchor=(0.0, -0.20), fontsize=6.3, ncol=2, frameon=False)
50 # (b) écart mensuel selon l'ancienneté, par type 50 # (b) écart mensuel selon l'ancienneté, par type
51 marq = {"3½": "o", "4½": "s", "5½": "^"} 51 marq = {"3½": "o", "4½": "s", "5½": "^"}
52 for i, (p, m) in enumerate(marq.items()): 52 for i, (p, m) in enumerate(marq.items()):
@@ -57,9 +57,8 @@ def fig_anciennete(res):
57 a2.yaxis.set_major_formatter(dollars); a2.set_xlabel("Ancienneté du locataire (années)"); a2.set_ylabel("Écart marché − contrat ($/mois)") 57 a2.yaxis.set_major_formatter(dollars); a2.set_xlabel("Ancienneté du locataire (années)"); a2.set_ylabel("Écart marché − contrat ($/mois)")
58 a2.set_title("(b) Écart mensuel selon l'ancienneté", loc="left"); a2.legend(title="Type", loc="upper left", fontsize=7) 58 a2.set_title("(b) Écart mensuel selon l'ancienneté", loc="left"); a2.legend(title="Type", loc="upper left", fontsize=7)
59 a2.set_xlim(0, 20); a2.set_ylim(0, 360) 59 a2.set_xlim(0, 20); a2.set_ylim(0, 360)
60− a2.legend(title="Type", loc="upper left", fontsize=7, bbox_to_anchor=(0.0, -0.24), ncol=3) 60+ a2.text(1.0, -0.36, SYNTH, transform=a2.transAxes, ha="right", va="bottom", fontsize=7, color=palette.ink2)
61− fig.text(0.99, -0.30, SYNTH, ha="right", va="bottom", fontsize=7, color=palette.ink2) 61+ fig.subplots_adjust(wspace=0.38)
62− fig.tight_layout()
63 return savefig(fig, "ch26/ch26-fig-anciennete.pdf") 62 return savefig(fig, "ch26/ch26-fig-anciennete.pdf")
64 63
65 64
modified analysis/python/figures/ch27_figures.py +9 −9
@@ -50,9 +50,9 @@ def fig_inoccupation_rmr(res):
50 50
51 def fig_cascade(res): 51 def fig_cascade(res):
52 r = res["cas3"] 52 r = res["cas3"]
53− etapes = [("RBP aux loyers\nde marché", r["rbp_marche"], "total"), ("Déficit de loyer\n(baux en place)", -r["deficit_loyer"], "delta"), 53+ etapes = [("RBP au\nmarché", r["rbp_marche"], "total"), ("Déficit\nde loyer", -r["deficit_loyer"], "delta"),
54− ("RBP\ncontractuel", r["rbp_contrat"], "total"), ("Inoccupation et\nmauvaises créances", -r["vacance_pertes"], "delta"), 54+ ("RBP\ncontractuel", r["rbp_contrat"], "total"), ("Inoccupation\net créances", -r["vacance_pertes"], "delta"),
55− ("Loyers\neffectifs", r["loyers_effectifs"], "total"), ("Autres\nrevenus", r["autres_revenus"], "delta"), ("Revenu brut\neffectif (RBE)", r["rbe"], "total")] 55+ ("Loyers\neffectifs", r["loyers_effectifs"], "total"), ("Autres\nrevenus", r["autres_revenus"], "delta"), ("RBE", r["rbe"], "total")]
56 fig, ax = plt.subplots(figsize=(LARGEUR, 3.5)) 56 fig, ax = plt.subplots(figsize=(LARGEUR, 3.5))
57 cum = 0.0 57 cum = 0.0
58 for i, (lab, val, kind) in enumerate(etapes): 58 for i, (lab, val, kind) in enumerate(etapes):
@@ -79,7 +79,7 @@ def fig_cascade(res):
79 def fig_trajectoires(res): 79 def fig_trajectoires(res):
80 t = res["trajectoires"]["table"]; h = res["trajectoires"]["hyp"]; T = res["trajectoires"]["T"] 80 t = res["trajectoires"]["table"]; h = res["trajectoires"]["hyp"]; T = res["trajectoires"]["T"]
81 t = t[t["annee"] <= T] 81 t = t[t["annee"] <= T]
82− fig, ax = plt.subplots(figsize=(LARGEUR, 3.3)) 82+ fig, ax = plt.subplots(figsize=(LARGEUR, 3.9))
83 x = t["exercice"].values 83 x = t["exercice"].values
84 ax.fill_between(x, t["tal_seul"] / 1000, t["marche"] / 1000, color=palette.grid, alpha=0.8, label="Écart entre l'indexation seule et le marché") 84 ax.fill_between(x, t["tal_seul"] / 1000, t["marche"] / 1000, color=palette.grid, alpha=0.8, label="Écart entre l'indexation seule et le marché")
85 ax.plot(x, t["marche"] / 1000, color=palette.cat[1], marker="s", ms=3.2, label=f"RBP au loyer de marché (indice : +{h['croissance_loyers_marche']*100:.1f} %/an)".replace(".", ",")) 85 ax.plot(x, t["marche"] / 1000, color=palette.cat[1], marker="s", ms=3.2, label=f"RBP au loyer de marché (indice : +{h['croissance_loyers_marche']*100:.1f} %/an)".replace(".", ","))
@@ -90,9 +90,9 @@ def fig_trajectoires(res):
90 ax.text(x[-1] + 0.15, t[k].iloc[-1] / 1000 + dy / 10, fmt_milliers(t[k].iloc[-1]) + " $", fontsize=7, color=palette.ink, va="center") 90 ax.text(x[-1] + 0.15, t[k].iloc[-1] / 1000 + dy / 10, fmt_milliers(t[k].iloc[-1]) + " $", fontsize=7, color=palette.ink, va="center")
91 ax.set_xticks(x); ax.set_xlabel("Exercice (1er septembre → 31 août)"); ax.set_ylabel("Revenu brut potentiel (milliers de $)") 91 ax.set_xticks(x); ax.set_xlabel("Exercice (1er septembre → 31 août)"); ax.set_ylabel("Revenu brut potentiel (milliers de $)")
92 ax.yaxis.set_major_formatter(FuncFormatter(lambda v, p: fmt_milliers(v))) 92 ax.yaxis.set_major_formatter(FuncFormatter(lambda v, p: fmt_milliers(v)))
93− ax.set_xlim(x[0] - 0.3, x[-1] + 1.4); ax.legend(loc="lower right", fontsize=7) 93+ ax.set_xlim(x[0] - 0.3, x[-1] + 1.4); ax.legend(loc="upper center", bbox_to_anchor=(0.5, -0.17), ncol=2, fontsize=6.8)
94 ax.set_title("Cas 3 : quatre trajectoires du revenu brut potentiel sur dix ans", loc="left") 94 ax.set_title("Cas 3 : quatre trajectoires du revenu brut potentiel sur dix ans", loc="left")
95− ax.text(0.99, -0.22, "DONNÉES SYNTHÉTIQUES", transform=ax.transAxes, ha="right", va="bottom", fontsize=7, color=palette.ink2) 95+ ax.text(0.99, -0.40, "DONNÉES SYNTHÉTIQUES", transform=ax.transAxes, ha="right", va="bottom", fontsize=7, color=palette.ink2)
96 return savefig(fig, "ch27/ch27-fig-trajectoires.pdf") 96 return savefig(fig, "ch27/ch27-fig-trajectoires.pdf")
97 97
98 98
@@ -100,16 +100,16 @@ def fig_sensibilite(res):
100 s = res["schl_rmr"]; base = res["cas3"] 100 s = res["schl_rmr"]; base = res["cas3"]
101 fig, (a, b) = plt.subplots(1, 2, figsize=(LARGEUR, 3.2)) 101 fig, (a, b) = plt.subplots(1, 2, figsize=(LARGEUR, 3.2))
102 d = np.linspace(0, 3, 61) 102 d = np.linspace(0, 3, 61)
103− rots = [(0.05, "ρ = 5 %"), (0.094, "ρ = 9,4 % (Montréal 2025)"), (0.13, "ρ = 13 % (Québec 2025)"), (0.162, "ρ = 16,2 % (Gatineau 2025)"), (0.25, "ρ = 25 %")] 103+ rots = [(0.05, "ρ = 5 %"), (0.094, "ρ = 9,4 % (Montréal 2025)"), (0.13, "ρ = 13 % (Québec 2025)"), (0.162, "ρ = 16,2 % (Gatineau 2025)"), (0.20, "ρ = 20 %")]
104 for i, (rho, lab) in enumerate(rots): 104 for i, (rho, lab) in enumerate(rots):
105 a.plot(d, [fin.taux_vacance_frictionnelle(rho, x) * 100 for x in d], color=palette.cat[i], label=lab) 105 a.plot(d, [fin.taux_vacance_frictionnelle(rho, x) * 100 for x in d], color=palette.cat[i], label=lab)
106 a.axhline(s.loc["Montréal", "inocc_2025"], color=palette.ink2, lw=0.7, ls=":") 106 a.axhline(s.loc["Montréal", "inocc_2025"], color=palette.ink2, lw=0.7, ls=":")
107− a.text(0.05, s.loc["Montréal", "inocc_2025"] + 0.08, "inoccupation SCHL, RMR de Montréal, oct. 2025 : 2,9 %", fontsize=6.8, color=palette.ink2) 107+ a.text(0.05, s.loc["Montréal", "inocc_2025"] - 0.12, "SCHL, RMR de Montréal, oct. 2025 : 2,9 %", fontsize=6.3, color=palette.ink2, va="top")
108 a.scatter([1.0], [base["v_frictionnel_dossier"] * 100], color=palette.ink, marker="*", s=70, zorder=4) 108 a.scatter([1.0], [base["v_frictionnel_dossier"] * 100], color=palette.ink, marker="*", s=70, zorder=4)
109 a.annotate("dossier du cas 3 :\nρ = 15 %, d = 1 mois\n→ 1,25 %", (1.0, base["v_frictionnel_dossier"] * 100), xytext=(1.25, 0.35), fontsize=6.8, 109 a.annotate("dossier du cas 3 :\nρ = 15 %, d = 1 mois\n→ 1,25 %", (1.0, base["v_frictionnel_dossier"] * 100), xytext=(1.25, 0.35), fontsize=6.8,
110 arrowprops=dict(arrowstyle="-", color=palette.ink2, lw=0.6)) 110 arrowprops=dict(arrowstyle="-", color=palette.ink2, lw=0.6))
111 a.set_xlabel("Durée moyenne de vacance entre deux baux, d (mois)"); a.set_ylabel("Inoccupation frictionnelle v_f = ρ·d/12 (%)") 111 a.set_xlabel("Durée moyenne de vacance entre deux baux, d (mois)"); a.set_ylabel("Inoccupation frictionnelle v_f = ρ·d/12 (%)")
112− a.set_ylim(0, 6.5); a.yaxis.set_major_formatter(pct1); a.legend(fontsize=6.5, loc="upper left") 112+ a.set_ylim(0, 5.2); a.yaxis.set_major_formatter(pct1); a.legend(fontsize=6.5, loc="upper left")
113 a.set_title("(a) Rotation, durée de vacance et inoccupation", loc="left", fontsize=8.5) 113 a.set_title("(a) Rotation, durée de vacance et inoccupation", loc="left", fontsize=8.5)
114 sv = c.sensibilite_v(base, taux=tuple(sorted(set(np.round(np.arange(0.01, 0.0801, 0.0025), 4)) | {0.029, 0.039, 0.059}))) 114 sv = c.sensibilite_v(base, taux=tuple(sorted(set(np.round(np.arange(0.01, 0.0801, 0.0025), 4)) | {0.029, 0.039, 0.059})))
115 b.plot(sv["v"] * 100, sv["valeur"] / 1000, color=palette.cat[0]) 115 b.plot(sv["v"] * 100, sv["valeur"] / 1000, color=palette.cat[0])
added analysis/python/figures/ch28_figures.py +144 −0
@@ -0,0 +1,144 @@
1+#!/usr/bin/env python3
2+"""Chapitre 28 — figures (5) : cascade RBP → RNE (cas 3) ; structure des dépenses en % du RBE (cas 3 vs cas 2) ;
3+évolution 2023-2025 des groupes de dépenses (indice 2023 = 100) et montant normalisé 2026 ; ratio de dépenses vs loyer
4+moyen (40 comparables du cas 3, courbe iso-dépenses du sujet) ; sensibilité du RNE (réserve par logement, taux de taxe,
5+gestion). DONNÉES SYNTHÉTIQUES (graine 20261031). Idempotent ; écrit dans assets/figures/ch28/ch28-fig-<slug>.pdf.
6+Usage (racine) : PYTHONPATH=analysis/python python3 analysis/python/figures/ch28_figures.py"""
7+from __future__ import annotations
8+import sys, pathlib
9+import numpy as np, pandas as pd
10+import matplotlib.pyplot as plt
11+from matplotlib.ticker import FuncFormatter
12+sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1]))
13+from traite.style import setup, palette, savefig, LARGEUR, DEMI, fmt_dollars
14+sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases"))
15+from ch28_depenses import calculer, ratio_iso_depenses, GROUPES, ORDRE_GROUPES
16+
17+pct0 = FuncFormatter(lambda x, p: f"{x*100:.0f} %")
18+pct1 = FuncFormatter(lambda x, p: f"{x*100:.1f}".replace(".", ",") + " %")
19+kdol = FuncFormatter(lambda x, p: f"{x/1000:,.0f}".replace(",", " ") + " k$")
20+NOTE = "DONNÉES SYNTHÉTIQUES"
21+
22+
23+def fig_cascade(res):
24+ b = res["base"]; cl = res["classement"]
25+ etapes = [("Revenu brut\npotentiel", b["rbp_contrat"], "total"), ("Inoccupation\net pertes", -b["vacance_pertes"], "neg"), ("Autres\nrevenus", b["autres_revenus"], "pos"),
26+ ("Revenu brut\neffectif", b["rbe"], "total"), ("Dépenses\nfixes", -cl["fixes"], "neg"), ("Dépenses\nvariables", -cl["variables_hors_gestion"], "neg"),
27+ ("Gestion\nimputée", -cl["gestion"], "neg"), ("Réserve de\nremplacement", -cl["reserve"], "neg"), ("Revenu net\nd'exploitation", b["rne"], "total")]
28+ fig, ax = plt.subplots(figsize=(LARGEUR, 3.3))
29+ cum = 0.0
30+ for i, (nom, v, k) in enumerate(etapes):
31+ if k == "total":
32+ ax.bar(i, v, color=palette.cat[0], width=0.7); cum = v; top = v
33+ else:
34+ bas = cum + v if v < 0 else cum
35+ ax.bar(i, abs(v), bottom=bas, color=palette.cat[3] if v < 0 else palette.cat[2], width=0.7); cum += v; top = max(bas + abs(v), bas)
36+ ax.text(i, top + 2500, fmt_dollars(abs(v)) if k != "total" else fmt_dollars(v), ha="center", va="bottom", fontsize=7.2, color=palette.ink)
37+ if i < len(etapes) - 1:
38+ ax.plot([i + 0.35, i + 0.65], [cum, cum], color=palette.ink2, lw=0.6, ls=":")
39+ ax.set_xticks(range(len(etapes))); ax.set_xticklabels([e[0] for e in etapes], fontsize=7.2)
40+ ax.yaxis.set_major_formatter(kdol); ax.set_ylabel("Dollars par année (première année)"); ax.set_ylim(0, 195000); ax.grid(axis="x", visible=False)
41+ ax.set_title("Cas 3 : du revenu brut potentiel au revenu net d'exploitation normalisé", loc="left")
42+ ax.text(0.99, -0.28, NOTE, transform=ax.transAxes, ha="right", va="bottom", fontsize=7, color=palette.ink2)
43+ return savefig(fig, "ch28/ch28-fig-cascade.pdf")
44+
45+
46+def fig_structure(res):
47+ g3 = res["classement"]["groupes_pct_rbe"]; g2 = res["cas2"]["classement"]["groupes_pct_rbe"]
48+ fig, ax = plt.subplots(figsize=(LARGEUR, 3.4))
49+ y = np.arange(len(ORDRE_GROUPES)); h = 0.38
50+ ax.barh(y + h / 2, g3.values, height=h, color=palette.cat[0], label="Cas 3 — 12 logements, Montréal (ratio total 46,6 %)")
51+ ax.barh(y - h / 2, g2.values, height=h, color=palette.cat[1], label="Cas 2 — triplex, Québec (ratio total 45,7 %)")
52+ for yy, v in zip(y + h / 2, g3.values): ax.text(v + 0.003, yy, f"{v*100:.1f} %".replace(".", ","), va="center", fontsize=7.2)
53+ for yy, v in zip(y - h / 2, g2.values): ax.text(v + 0.003, yy, f"{v*100:.1f} %".replace(".", ","), va="center", fontsize=7.2)
54+ ax.set_yticks(y); ax.set_yticklabels(ORDRE_GROUPES); ax.invert_yaxis(); ax.xaxis.set_major_formatter(pct0); ax.set_xlim(0, 0.29)
55+ ax.set_xlabel("Dépense normalisée en pourcentage du revenu brut effectif"); ax.grid(axis="y", visible=False)
56+ ax.legend(loc="lower right", fontsize=7.5)
57+ ax.set_title("Structure des dépenses normalisées, par groupe de postes", loc="left")
58+ ax.text(0.99, -0.22, NOTE, transform=ax.transAxes, ha="right", va="bottom", fontsize=7, color=palette.ink2)
59+ return savefig(fig, "ch28/ch28-fig-structure.pdf")
60+
61+
62+def fig_evolution(res):
63+ dep = res["dep"].copy(); dep["groupe"] = dep["poste"].map(GROUPES)
64+ g = dep.groupby(["groupe", "annee"])["montant"].sum().unstack("annee").reindex(ORDRE_GROUPES).dropna(how="all")
65+ g = g[g[2023] > 0]
66+ det = res["detail"].assign(groupe=res["detail"]["poste"].map(GROUPES)).groupby("groupe")["normalise"].sum()
67+ fig, axes = plt.subplots(1, 2, figsize=(LARGEUR, 3.2), gridspec_kw={"width_ratios": [1.15, 1]})
68+ ax = axes[0]
69+ for i, (grp, r) in enumerate(g.iterrows()):
70+ idx = r / r[2023] * 100
71+ ax.plot([2023, 2024, 2025], idx.values, marker="o", color=palette.cat[i % 6], lw=1.4, ms=3.5, ls="-" if i < 6 else "--")
72+ ax.text(2025.07, idx[2025], grp, fontsize=6.8, va="center", color=palette.ink)
73+ tot = res["historique"].loc["Total", [2023, 2024, 2025]]; ax.plot([2023, 2024, 2025], (tot / tot[2023] * 100).values, color=palette.ink, lw=2.2, marker="s", ms=4)
74+ ax.text(2025.07, tot[2025] / tot[2023] * 100, "Total", fontsize=7.2, va="center", fontweight="bold")
75+ ax.set_xticks([2023, 2024, 2025]); ax.set_xlim(2022.8, 2026.1); ax.set_ylabel("Indice (2023 = 100)"); ax.set_title("Historique déclaré, indice par groupe", loc="left", fontsize=8.5)
76+ ax = axes[1]
77+ grp_order = [x for x in ORDRE_GROUPES if x in g.index or x in det.index]
78+ h25 = pd.Series({k: g.loc[k, 2025] if k in g.index else 0.0 for k in grp_order}); hn = det.reindex(grp_order).fillna(0.0)
79+ y = np.arange(len(grp_order)); w = 0.38
80+ ax.barh(y + w / 2, h25.values, height=w, color=palette.cat[4], label="Déclaré 2025")
81+ ax.barh(y - w / 2, hn.values, height=w, color=palette.cat[0], label="Normalisé (première année)")
82+ ax.set_yticks(y); ax.set_yticklabels(grp_order, fontsize=7); ax.invert_yaxis(); ax.xaxis.set_major_formatter(kdol); ax.grid(axis="y", visible=False)
83+ ax.legend(loc="lower right", fontsize=7); ax.set_title("2025 déclaré et montant normalisé", loc="left", fontsize=8.5)
84+ fig.text(0.99, -0.02, NOTE, ha="right", va="bottom", fontsize=7, color=palette.ink2)
85+ fig.tight_layout(w_pad=1.5)
86+ return savefig(fig, "ch28/ch28-fig-evolution.pdf")
87+
88+
89+def fig_ratio_loyer(res):
90+ c = res["comps"]; b = res["base"]; hyp = res["hyp"]; m = res["marche"]
91+ fig, ax = plt.subplots(figsize=(LARGEUR, 3.3))
92+ ax.scatter(c["loyer_mensuel_moyen"], c["ratio_depenses"], s=26 + 1.6 * c["nb_logements"], color=palette.cat[0], alpha=0.75, edgecolor=palette.surface, lw=0.6,
93+ label="Ventes comparables (taille du point = nombre de logements)")
94+ L = np.linspace(950, 1650, 200); ax.plot(L, ratio_iso_depenses(L, b, hyp), color=palette.cat[3], lw=1.6, label="Sujet : ratio si le loyer moyen était L (dépenses inchangées)")
95+ ax.scatter([m["loyer_moyen_contrat"]], [b["ratio_depenses"]], marker="D", s=70, color=palette.cat[3], edgecolor=palette.ink, lw=1.2, zorder=5)
96+ ax.scatter([m["loyer_moyen"]], [m["ratio"]], marker="D", s=70, facecolor="white", edgecolor=palette.cat[3], lw=1.4, zorder=5)
97+ ax.annotate("sujet, loyers contractuels\n1 150 $ ; 46,6 %", (m["loyer_moyen_contrat"], b["ratio_depenses"]), xytext=(985, 0.50), fontsize=7.3, arrowprops=dict(arrowstyle="-", color=palette.ink2, lw=0.7))
98+ ax.annotate("sujet, loyers de marché\n1 292 $ ; 42,1 %", (m["loyer_moyen"], m["ratio"]), xytext=(1380, 0.47), fontsize=7.3, arrowprops=dict(arrowstyle="-", color=palette.ink2, lw=0.7))
99+ med = res["comps_stats"]["ratio_median"]; ax.axhline(med, color=palette.ink2, lw=0.8, ls=":"); ax.text(1640, med + 0.004, f"médiane des comparables {med*100:.1f} %".replace(".", ","), ha="right", fontsize=7)
100+ ax.xaxis.set_major_formatter(FuncFormatter(lambda x, p: f"{x:,.0f} $".replace(",", " "))); ax.yaxis.set_major_formatter(pct0)
101+ ax.set_xlabel("Loyer mensuel moyen par logement"); ax.set_ylabel("Dépenses (réserve comprise) / revenu brut effectif"); ax.set_xlim(950, 1650); ax.set_ylim(0.28, 0.56)
102+ ax.legend(loc="upper center", bbox_to_anchor=(0.5, -0.2), ncol=1, fontsize=7.5)
103+ ax.set_title("Ratio de dépenses et niveau des loyers : quarante ventes et le sujet du cas 3", loc="left")
104+ ax.text(0.99, -0.42, NOTE, transform=ax.transAxes, ha="right", va="bottom", fontsize=7, color=palette.ink2)
105+ return savefig(fig, "ch28/ch28-fig-ratio-loyer.pdf")
106+
107+
108+def fig_sensibilite(res):
109+ s = res["sensibilites"]; noi0 = res["base"]["rne"]
110+ fig, axes = plt.subplots(1, 3, figsize=(LARGEUR, 2.9), sharey=True)
111+ # réserve
112+ ax = axes[0]; r = s["reserve"]
113+ ax.plot(r["reserve_par_logement"], r["rne"], color=palette.cat[0], marker="o", ms=3.5)
114+ ax.axvline(675.25, color=palette.ink2, lw=0.7, ls=":"); ax.set_xlabel("Réserve ($ par logement par an)"); ax.set_title("Réserve de remplacement", loc="left", fontsize=8.5)
115+ ax.text(690, noi0 + 300, "675 $ (dossier)", fontsize=7)
116+ # taxes : taux continu
117+ ax = axes[1]; taux = np.linspace(0.0040, 0.0080, 50); role = res["hyp"]["valeur_role_2026"]; taxe0 = role * res["hyp"]["taux_taxe_fonciere"]
118+ ax.plot(taux * 100, noi0 - (role * taux - taxe0), color=palette.cat[1])
119+ for _, row in s["taxe"].iterrows():
120+ if row["taux_par_100"] <= 0.80: ax.scatter([row["taux_par_100"]], [row["rne"]], color=palette.cat[1], s=22, zorder=4)
121+ ax.axvline(0.4631, color=palette.ink2, lw=0.7, ls=":"); ax.set_xlabel("Taxe municipale ($ par 100 $ du rôle)"); ax.set_title("Taux de taxe (rôle inchangé)", loc="left", fontsize=8.5)
122+ ax.xaxis.set_major_formatter(FuncFormatter(lambda x, p: f"{x:.2f}".replace(".", ",")))
123+ ax.text(0.48, noi0 + 300, "Montréal 0,4631", fontsize=7); ax.text(0.70, noi0 - 6200, "Gatineau 6+\n0,6964", fontsize=6.8); ax.text(0.715, noi0 - 9500, "Québec 6+\n0,7697", fontsize=6.8, ha="left")
124+ # gestion
125+ ax = axes[2]; g = s["gestion"]
126+ ax.plot(g["gestion_pct"], g["rne"], color=palette.cat[2], marker="o", ms=3.5)
127+ ax.axvline(0.04, color=palette.ink2, lw=0.7, ls=":"); ax.set_xlabel("Gestion imputée (% du RBE)"); ax.set_title("Taux de gestion", loc="left", fontsize=8.5)
128+ ax.xaxis.set_major_formatter(pct0); ax.text(0.045, noi0 + 300, "4 % (dossier)", fontsize=7)
129+ for ax in axes:
130+ ax.axhline(noi0, color=palette.ink2, lw=0.7, ls=":"); ax.yaxis.set_major_formatter(kdol)
131+ axes[0].set_ylabel("RNE normalisé ($/an)")
132+ fig.text(0.99, -0.04, NOTE, ha="right", va="bottom", fontsize=7, color=palette.ink2)
133+ fig.tight_layout(w_pad=1.2)
134+ return savefig(fig, "ch28/ch28-fig-sensibilite.pdf")
135+
136+
137+def main():
138+ setup(); res = calculer()
139+ outs = [fig_cascade(res), fig_structure(res), fig_evolution(res), fig_ratio_loyer(res), fig_sensibilite(res)]
140+ for o in outs: print("écrit :", o)
141+
142+
143+if __name__ == "__main__":
144+ main()
added analysis/python/figures/ch32_figures.py +128 −0
@@ -0,0 +1,128 @@
1+"""Figures du chapitre 32 — Duplex à quintuplex (données SYNTHÉTIQUES du cas 2, sauf la figure des médianes APCIQ, citée).
2+Produit : assets/figures/ch32/ch32-fig-{mrb-taille-quartier,taux-loyer-relatif,prix-logement-quartier,plex-apciq-rmr,sensibilite-occupant}.pdf
3+Exécution : python3 analysis/python/figures/ch32_figures.py (depuis la racine)."""
4+from __future__ import annotations
5+import sys, pathlib
6+import numpy as np
7+import matplotlib.pyplot as plt
8+from matplotlib.ticker import FuncFormatter
9+
10+ROOT = pathlib.Path(__file__).resolve().parents[3]
11+sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases"))
12+from traite.style import setup, palette, savefig, LARGEUR, DEMI, fmt_dollars, fmt_milliers # noqa: E402
13+import ch32_plex as C # noqa: E402
14+
15+QUARTIERS = ["Limoilou", "Saint-Roch", "Saint-Sauveur", "Maizerets"]
16+
17+
18+def _boxstyle(bp, color):
19+ for k in ("boxes", "whiskers", "caps", "medians"):
20+ for a in bp[k]: a.set_color(color); a.set_linewidth(1.2)
21+ for a in bp["medians"]: a.set_color(palette.ink); a.set_linewidth(1.6)
22+ for a in bp["fliers"]: a.set(marker="o", markersize=3, markerfacecolor="none", markeredgecolor=color, alpha=0.7)
23+
24+
25+def fig_mrb(R):
26+ c = R["comparables"]
27+ fig, axes = plt.subplots(1, 2, figsize=(LARGEUR, 3.0), gridspec_kw={"width_ratios": [1, 2]})
28+ ax = axes[0]
29+ data = [c.loc[c["nb_logements"] == n, "mrb"] for n in (2, 3)]
30+ bp = ax.boxplot(data, widths=0.55, patch_artist=False); _boxstyle(bp, palette.cat[0])
31+ ax.set_xticks([1, 2]); ax.set_xticklabels([f"Duplex\n(n = {len(data[0])})", f"Triplex\n(n = {len(data[1])})"])
32+ ax.set_ylabel("Multiplicateur du revenu brut (MRB)"); ax.set_title("Selon la taille", loc="left")
33+ for i, d in enumerate(data, 1):
34+ ax.text(i + 0.33, d.median(), f"{d.median():.1f}".replace(".", ","), va="center", fontsize=8, color=palette.ink)
35+ ax = axes[1]
36+ data = [c.loc[c["quartier"] == q, "mrb"] for q in QUARTIERS]
37+ bp = ax.boxplot(data, widths=0.55); _boxstyle(bp, palette.cat[1])
38+ ax.set_xticks(range(1, 5)); ax.set_xticklabels([f"{q}\n(n = {len(d)})" for q, d in zip(QUARTIERS, data)])
39+ ax.set_title("Selon le quartier (2 et 3 logements confondus)", loc="left")
40+ for i, d in enumerate(data, 1):
41+ ax.text(i + 0.33, d.median(), f"{d.median():.1f}".replace(".", ","), va="center", fontsize=8, color=palette.ink)
42+ ymin = min(c["mrb"]) - 1; ymax = max(c["mrb"]) + 1
43+ for ax in axes: ax.set_ylim(ymin, ymax); ax.grid(axis="x", visible=False)
44+ fig.tight_layout(); savefig(fig, "ch32/ch32-fig-mrb-taille-quartier.pdf")
45+
46+
47+def fig_taux_rho(R):
48+ g = R["decomposition"]; reg = R["regression"]; I = R["indications"]
49+ fig, ax = plt.subplots(figsize=(LARGEUR, 3.3))
50+ for n, col, lab in ((2, palette.cat[0], "Duplex"), (3, palette.cat[1], "Triplex")):
51+ s = g[g["nb_logements"] == n]
52+ ax.scatter(s["loyer_relatif"], 100 * s["taux_global"], s=16, color=col, alpha=0.75, label=lab, edgecolors="none")
53+ xs = np.linspace(g["loyer_relatif"].min() - 0.01, 1.01, 50)
54+ ax.plot(xs, 100 * (reg["a"] + reg["b_rho"] * (xs - 1)), color=palette.ink, linewidth=1.4, label="Régression $R = a + b(\\rho - 1)$")
55+ ax.axhline(100 * R["indicateurs"]["taux_median"], color=palette.ink2, linestyle=":", linewidth=1)
56+ ax.text(xs[0], 100 * R["indicateurs"]["taux_median"] + 0.04, "médiane des 127 ventes", fontsize=7.5, color=palette.ink2)
57+ ax.scatter([I["rho_sujet"]], [100 * I["taux_ajuste"]], marker="*", s=140, color=palette.cat[2], zorder=5, label="Sujet : $\\rho$ = 0,848, taux ajusté", edgecolors=palette.ink, linewidths=0.5)
58+ ax.set_xlabel("Loyer contractuel moyen / loyer de marché estimé ($\\rho$)"); ax.set_ylabel("Taux global extrait (%)")
59+ ax.yaxis.set_major_formatter(FuncFormatter(lambda x, p: f"{x:.1f}".replace(".", ",")))
60+ ax.xaxis.set_major_formatter(FuncFormatter(lambda x, p: f"{x:.2f}".replace(".", ",")))
61+ ax.legend(loc="upper left", fontsize=7.5, ncol=2); fig.tight_layout()
62+ savefig(fig, "ch32/ch32-fig-taux-loyer-relatif.pdf")
63+
64+
65+def fig_prix_logement(R):
66+ c = R["comparables"]; comp5 = R["comparables5"]; I = R["indications"]
67+ fig, ax = plt.subplots(figsize=(LARGEUR, 3.2))
68+ data = [c.loc[c["quartier"] == q, "prix_par_logement"] / 1000 for q in QUARTIERS]
69+ bp = ax.boxplot(data, widths=0.55); _boxstyle(bp, palette.cat[0])
70+ rng = np.random.default_rng(20261031)
71+ for i, q in enumerate(QUARTIERS, 1):
72+ s = c[c["quartier"] == q]
73+ ax.scatter(i + rng.uniform(-0.18, 0.18, len(s)), s["prix_par_logement"] / 1000, s=9, color=palette.cat[0], alpha=0.35, edgecolors="none")
74+ ax.scatter(np.ones(len(comp5)) + 0.0, comp5["prix_par_logement"] / 1000, marker="D", s=30, color=palette.cat[2], zorder=5, label="Cinq comparables retenus (triplex, Limoilou)", edgecolors=palette.ink, linewidths=0.4)
75+ ax.axhline(I["prix_log_final"] / 1000, color=palette.ink, linestyle="--", linewidth=1.1)
76+ ax.text(4.45, I["prix_log_final"] / 1000 + 4, "valeur pondérée\ndu sujet, par logement", fontsize=7.5, ha="right", color=palette.ink)
77+ ax.set_xticks(range(1, 5)); ax.set_xticklabels([f"{q}\n(n = {len(d)})" for q, d in zip(QUARTIERS, data)])
78+ ax.set_ylabel("Prix par logement (milliers de $)"); ax.yaxis.set_major_formatter(FuncFormatter(fmt_milliers))
79+ ax.grid(axis="x", visible=False); ax.legend(loc="upper right", fontsize=7.5); fig.tight_layout()
80+ savefig(fig, "ch32/ch32-fig-prix-logement-quartier.pdf")
81+
82+
83+# Médianes APCIQ (Centris) des plex de 2 à 5 logements, T3 2026 — chiffres cités (communiqué du 14 octobre 2026 et baromètres), sources/notes-E.md
84+APCIQ_PLEX_T3_2026 = [
85+ ("Province de Québec", 699_000), ("RMR de Montréal", 860_500), ("Île de Montréal", 900_000), ("RMR de Québec", 600_000),
86+ ("Agglomération de Québec", 618_500), ("Secteur La Cité–Basse-Ville et Limoilou", 647_500), ("RMR de Gatineau", 617_500),
87+ ("RMR de Sherbrooke", 544_500), ("RMR de Trois-Rivières", 420_000), ("RMR de Saguenay", 373_500)]
88+
89+
90+def fig_apciq():
91+ fig, ax = plt.subplots(figsize=(LARGEUR, 3.1))
92+ labels = [l for l, _ in APCIQ_PLEX_T3_2026][::-1]; vals = [v for _, v in APCIQ_PLEX_T3_2026][::-1]
93+ cols = [palette.cat[1] if ("Québec" in l and "Province" not in l) or "Limoilou" in l else palette.cat[0] for l in labels]
94+ ax.barh(labels, vals, color=cols, height=0.65)
95+ for i, v in enumerate(vals): ax.text(v + 8000, i, fmt_dollars(v), va="center", fontsize=7.5, color=palette.ink)
96+ ax.set_xlim(0, 1_050_000); ax.xaxis.set_major_formatter(FuncFormatter(lambda x, p: fmt_milliers(x / 1000) + " k$"))
97+ ax.set_xlabel("Prix médian des plex (2 à 5 logements), 3ᵉ trimestre 2026"); ax.grid(axis="y", visible=False); fig.tight_layout()
98+ savefig(fig, "ch32/ch32-fig-plex-apciq-rmr.pdf")
99+
100+
101+def fig_sensibilite(R):
102+ S = R["sensibilite"]; base = R["base"]; I = R["indications"]; loyers = R["loyers"]
103+ fig, axes = plt.subplots(1, 2, figsize=(LARGEUR, 3.1))
104+ ax = axes[0]; L = S["loyers"]
105+ ax.plot(L["rho"], L["v_mrb"] / 1000, marker="o", color=palette.cat[0], label="MRB des comparables × RBP")
106+ ax.plot(L["rho"], L["v_cap_taux_median"] / 1000, marker="s", color=palette.cat[1], label="Capitalisation, taux médian fixe")
107+ ax.plot(L["rho"], L["v_cap_ajuste"] / 1000, marker="^", color=palette.cat[2], label="Capitalisation, taux ajusté à $\\rho$")
108+ ax.axvline(I["rho_sujet"], color=palette.ink2, linestyle=":", linewidth=1); ax.text(I["rho_sujet"] + 0.004, L["v_mrb"].max() / 1000, "sujet", fontsize=7.5, color=palette.ink2, va="top")
109+ ax.set_xlabel("Loyers contractuels / loyers de marché ($\\rho$)"); ax.set_ylabel("Indication de valeur (milliers de $)")
110+ ax.xaxis.set_major_formatter(FuncFormatter(lambda x, p: f"{x:.2f}".replace(".", ","))); ax.yaxis.set_major_formatter(FuncFormatter(fmt_milliers))
111+ ax.legend(fontsize=7, loc="upper left"); ax.set_title("Loyers sous le marché", loc="left")
112+ ax = axes[1]
113+ prix = I["valeur_arrondie"]; taux = np.arange(0.0312, 0.0713, 0.0025)
114+ for mf, col, lab in ((C.mise_de_fonds_minimale_assuree(prix), palette.cat[0], "Mise de fonds minimale assurée (7,1 %)"), (0.20 * prix, palette.cat[1], "Mise de fonds de 20 %")):
115+ y = [C.cout_occupation(C.financement_occupant(prix, mf, t), base, loyers, "RC")["cout_net_mensuel_hors_capital"] for t in taux]
116+ ax.plot(100 * taux, y, color=col, label=lab)
117+ ax.axhline(1430, color=palette.ink2, linestyle="--", linewidth=1); ax.text(100 * taux[0] + 0.05, 1430 + 40, "loyer de marché du logement occupé (1 430 $)", fontsize=7.5, color=palette.ink2)
118+ ax.axvline(100 * C.TAUX_ASSURE, color=palette.ink2, linestyle=":", linewidth=1); ax.text(100 * C.TAUX_ASSURE + 0.05, 3000, "4,12 %\n(assuré,\naoût 2026)", fontsize=7, color=palette.ink2)
119+ ax.set_xlabel("Taux hypothécaire (%)"); ax.set_ylabel("Coût net mensuel d'occupation, hors capital ($)")
120+ ax.xaxis.set_major_formatter(FuncFormatter(lambda x, p: f"{x:.1f}".replace(".", ","))); ax.yaxis.set_major_formatter(FuncFormatter(fmt_milliers))
121+ ax.legend(fontsize=7, loc="upper left"); ax.set_title("Financement de l'acheteur-occupant", loc="left")
122+ fig.tight_layout(); savefig(fig, "ch32/ch32-fig-sensibilite-occupant.pdf")
123+
124+
125+if __name__ == "__main__":
126+ setup(); R = C.calculer()
127+ fig_mrb(R); fig_taux_rho(R); fig_prix_logement(R); fig_apciq(); fig_sensibilite(R)
128+ print("figures ch32 écrites dans", ROOT / "assets/figures/ch32")
modified analysis/python/tests/test_ch19.py +11 −0
@@ -234,3 +234,14 @@ def test_solutions_exercices():
234 co = np.array([[1, 0], [0, 2], [-3, 0.0]]); z = np.array([0.08, 0.05, -0.01]); p = {"pepite": 0.0045, "palier_partiel": 0.009, "portee": 5.0} 234 co = np.array([[1, 0], [0, 2], [-3, 0.0]]); z = np.array([0.08, 0.05, -0.01]); p = {"pepite": 0.0045, "palier_partiel": 0.009, "portee": 5.0}
235 k = h.krigeage_ordinaire(co, z, np.array([[0, 0.0]]), p, None) 235 k = h.krigeage_ordinaire(co, z, np.array([[0, 0.0]]), p, None)
236 assert k["prediction"][0] == pytest.approx(0.0489, abs=0.0001) and k["variance"][0] == pytest.approx(0.00869, abs=0.00001) 236 assert k["prediction"][0] == pytest.approx(0.0489, abs=0.0001) and k["variance"][0] == pytest.approx(0.00869, abs=0.00001)
237+ # ex. 19.1 (suite) : signes alternés → I = −1 exactement sur cette configuration
238+ e2 = np.array([0.10, -0.10, 0.10, -0.10, 0.10]); assert h.moran_i(e2, W)["I"] == pytest.approx(-1.0, abs=1e-9)
239+ # ex. 19.6 : poids du krigeage vs inverse de la distance
240+ idw = (1 / np.array([1, 2, 3.0])); idw /= idw.sum(); assert idw[2] == pytest.approx(0.182, abs=0.001) and (idw * z).sum() == pytest.approx(0.0555, abs=0.0001)
241+
242+def test_solution_exercice_7(res):
243+ r = c19.exercice_7(res["base"]); s = r["sem_par_k"].set_index("k")
244+ assert s.loc[4, "lambda"] == pytest.approx(0.5066, abs=0.0005) and s.loc[16, "lambda"] == pytest.approx(0.7855, abs=0.0005)
245+ assert s.loc[4, "u_sujet"] == pytest.approx(-0.0189, abs=0.0005) and s.loc[16, "u_sujet"] == pytest.approx(-0.0554, abs=0.0005)
246+ assert list(s["indication"].round(0)) == [656724, 633352, 628672]
247+ assert r["st_24"]["sans_voisin"] == 16 and r["st_24"]["rho_st"] == pytest.approx(0.4149, abs=0.0005) and r["st_12"]["rho_st"] == pytest.approx(0.3696, abs=0.0005)
added analysis/python/tests/test_ch24.py +211 −0
@@ -0,0 +1,211 @@
1+"""Tests du chapitre 24 — dépréciation et désuétudes du sujet du cas 1, extraction de marché, profils et exercices.
2+Tolérances : 0,01 $ sur les montants recalculés ; 0,5 $ sur les montants arrondis cités ; 5e-4 sur les taux cités en % à deux
3+décimales ; 1e-9 sur les identités."""
4+import sys, pathlib
5+import numpy as np, pytest
6+sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases"))
7+import ch24_depreciation as cas
8+from traite import cout as ct
9+
10+
11+@pytest.fixture(scope="module")
12+def 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"])}
21+
22+
23+class 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"] == 60
28+ assert a["indice_etat"] == pytest.approx(2.809, abs=5e-4) and a["age_effectif_pondere"] == pytest.approx(32.8, abs=0.05)
29+
30+ 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 = 29
32+ import ch11_inspection as c11
33+ 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.0
34+ assert ct.vie_restante(60, 29) == 31.0 and ct.vie_restante(60, 75) == 0.0
35+
36+
37+class 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"]) == 18
49+
50+ 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ée
55+ 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.0
59+ ecart = ve["curable"] * (1 - rS) + sum(ve["courte_duree"].values()) - courtes["cout_neuf"].sum() * rS
60+ 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)
66+
67+
68+class 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)
73+
74+ 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)
79+
80+
81+class 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.0
86+ 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()) == 33
90+
91+ def test_identite_extraction(self, data):
92+ w = data["w"]
93+ assert len(w) == 600
94+ 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)
96+
97+ 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)
109+
110+
111+class 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.0
118+ # conversion : d_B (1 − α) = d_total
119+ assert np.allclose(pr["hedonique_bat"] * (1 - pr["alpha"]), pr["hedonique_total"])
120+ # profils croissants sur 0-80 ans
121+ assert np.all(np.diff(pr["extraction"]) > 0) and np.all(np.diff(pr["hedonique_bat"]) > 0)
122+
123+ 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)
125+
126+
127+class 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.0
135+ # identité V = V_L + C_N − D_phys − D_fonc − D_ext
136+ assert B["valeur"] == pytest.approx(277000 + 397567.33 - 87461.05 - 7605.31, abs=0.02)
137+
138+ 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éduction
141+ 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)
143+
144+
145+class 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)
150+
151+ 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)
157+
158+ 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)
165+
166+
167+class TestExercices:
168+ def test_ex1(self):
169+ assert ct.depreciation_age_vie(320000, 15, 60) == 80000 and ct.vie_restante(60, 15) == 45
170+ 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)
173+
174+ 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"] == 282500
178+ 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 == 5275
180+
181+ 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èvement
184+
185+ 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)
189+
190+ 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)
200+
201+ def test_ex6(self):
202+ b1, b2, al = -0.005097, 0.00002642, 0.384
203+ 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)
208+
209+ 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)
added analysis/python/tests/test_ch27.py +183 −0
@@ -0,0 +1,183 @@
1+"""Tests du chapitre 27 — revenus potentiels, vacance et pertes (cas 3 et cas 2, données synthétiques ; tableaux SCHL réels).
2+Identités (RBE, cascade, modèle frictionnel, concessions, trajectoires) + valeurs citées dans manuscript/chapters/ch27.tex et
3+solutions/ch27.tex (tolérances déclarées : 0,01 $ sur les montants, 1e-6 sur les taux, 0,05 point sur les lectures SCHL)."""
4+import sys, pathlib
5+sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1]))
6+sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases"))
7+import numpy as np, pytest
8+from traite import cas2, cas3, finance as fin
9+import ch27_revenus as c
10+
11+ROOT = pathlib.Path(__file__).resolve().parents[3]
12+
13+
14+@pytest.fixture(scope="module")
15+def res():
16+ return c.calculer()
17+
18+
19+# ----------------------------------------------------------------------------- bibliothèque : fonctions ajoutées pour le chapitre
20+def test_vacance_frictionnelle_identites():
21+ assert fin.taux_vacance_frictionnelle(0.15, 1.0) == pytest.approx(0.0125)
22+ assert fin.taux_vacance_frictionnelle(0.094, 12.0) == pytest.approx(0.094) # une année entière de vacance = la rotation
23+ assert fin.taux_vacance_frictionnelle(0.0, 3.0) == 0.0 and fin.taux_vacance_frictionnelle(0.2, 0.0) == 0.0
24+ # linéarité : doubler la durée ou la rotation double la provision
25+ assert fin.taux_vacance_frictionnelle(0.1, 2.0) == pytest.approx(2 * fin.taux_vacance_frictionnelle(0.1, 1.0)) == pytest.approx(2 * fin.taux_vacance_frictionnelle(0.05, 2.0))
26+ with pytest.raises(ValueError):
27+ fin.taux_vacance_frictionnelle(-0.1, 1.0)
28+
29+
30+def test_loyer_effectif_concession():
31+ assert fin.loyer_effectif_concession(1300, 1, 12) == pytest.approx(1300 * 11 / 12)
32+ assert fin.loyer_effectif_concession(1300, 0, 12) == 1300 and fin.loyer_effectif_concession(1300, 12, 12) == 0
33+ # un mois sur douze ≡ deux mois sur vingt-quatre
34+ assert fin.loyer_effectif_concession(1300, 1, 12) == pytest.approx(fin.loyer_effectif_concession(1300, 2, 24))
35+ with pytest.raises(ValueError):
36+ fin.loyer_effectif_concession(1300, 13, 12)
37+
38+
39+# ----------------------------------------------------------------------------- cas 3 : identités et valeurs citées
40+def test_cascade_cas3_identites(res):
41+ r = res["cas3"]
42+ assert r["rbp_contrat"] == pytest.approx(12 * r["etat"]["loyer_mensuel_contrat"].sum())
43+ assert r["rbp_marche"] == pytest.approx(12 * r["etat"]["loyer_mensuel_marche"].sum())
44+ assert r["rbe"] == pytest.approx(fin.revenu_brut_effectif(r["rbp_contrat"], r["taux_vacance_pertes"], r["autres_revenus"]))
45+ assert r["rbe"] == pytest.approx(r["loyers_effectifs"] + r["autres_revenus"])
46+ assert r["loyers_effectifs"] == pytest.approx(r["rbp_contrat"] - r["vacance_pertes"])
47+ assert r["rbp_marche"] - r["deficit_loyer"] == pytest.approx(r["rbp_contrat"])
48+ assert r["taux_occupation_economique"] == pytest.approx((1 - r["taux_vacance_pertes"]) * r["rbp_contrat_sur_marche"])
49+ # cohérence avec le module partagé (ch. 29, 30, 47)
50+ t = cas3.tout()
51+ assert r["rbe"] == pytest.approx(t["base"]["rbe"], rel=1e-12) and r["rbp_contrat"] == pytest.approx(t["base"]["rbp_contrat"], rel=1e-12)
52+
53+
54+def test_valeurs_citees_cas3(res):
55+ r = res["cas3"]
56+ assert r["loyer_mensuel_contrat"] == 13795 and r["loyer_mensuel_marche"] == 15503
57+ assert r["rbp_contrat"] == pytest.approx(165_540, abs=0.01) and r["rbp_marche"] == pytest.approx(186_036, abs=0.01)
58+ assert r["deficit_loyer"] == pytest.approx(20_496, abs=0.01) and r["deficit_pct"] == pytest.approx(0.1102, abs=5e-5)
59+ assert r["vacance_pertes"] == pytest.approx(6_456.06, abs=0.01) and r["loyers_effectifs"] == pytest.approx(159_083.94, abs=0.01)
60+ assert r["autres_revenus"] == pytest.approx(9_360, abs=0.01) and r["autres_par_logement_mois"] == pytest.approx(65, abs=0.01)
61+ assert r["rbe"] == pytest.approx(168_443.94, abs=0.01) and r["rbe_sur_rbp"] == pytest.approx(1.0175, abs=5e-5)
62+ assert r["rbe_par_logement"] == pytest.approx(14_037.00, abs=0.01) and r["rbe_par_m2"] == pytest.approx(204.17, abs=0.01)
63+ assert r["taux_occupation_economique"] == pytest.approx(0.8551, abs=5e-5)
64+ assert r["rbe_marche"] == pytest.approx(188_140.60, abs=0.01)
65+ assert r["rbe_variante_v_sur_tout"] == pytest.approx(168_078.90, abs=0.01) and r["ecart_variante_v_sur_tout"] == pytest.approx(-365.04, abs=0.01)
66+ assert r["v_frictionnel_dossier"] == pytest.approx(0.0125, abs=1e-9) and r["v_residuel_dossier"] == pytest.approx(0.0265, abs=1e-9)
67+
68+
69+def test_par_type_cas3(res):
70+ p = res["par_type"]
71+ assert p.loc["3½", "n"] == 4 and p.loc["4½", "n"] == 6 and p.loc["5½", "n"] == 2
72+ assert p.loc["3½", "ecart_pct"] == pytest.approx(15.76, abs=0.01) and p.loc["4½", "ecart_pct"] == pytest.approx(11.07, abs=0.01)
73+ assert p.loc["5½", "ecart_pct"] == pytest.approx(4.93, abs=0.01) and p.loc["Total", "ecart_pct"] == pytest.approx(11.02, abs=0.01)
74+ assert p.loc["3½", "anciennete"] == pytest.approx(9.0) and p.loc["Total", "anciennete"] == pytest.approx(6.33, abs=0.01)
75+ assert p.loc["Total", "rbp_contrat"] == pytest.approx(165_540) and p.loc["Total", "rbp_marche"] == pytest.approx(186_036)
76+
77+
78+# ----------------------------------------------------------------------------- cas 2
79+def test_cascade_cas2(res):
80+ r = res["cas2"]
81+ t = cas2.tout()
82+ assert r["rbe"] == pytest.approx(t["base"]["rbe"], rel=1e-12)
83+ assert r["rbp_contrat"] == pytest.approx(41_832) and r["rbp_marche"] == pytest.approx(49_332) and r["ecart_marche"] == pytest.approx(7_500)
84+ assert r["deficit_pct"] == pytest.approx(0.1520, abs=5e-5) and r["vacance_pertes"] == pytest.approx(1_254.96, abs=0.01)
85+ assert r["rbe"] == pytest.approx(40_577.04, abs=0.01) and r["rbe_marche"] == pytest.approx(47_852.04, abs=0.01)
86+ assert r["taux_occupation_economique"] == pytest.approx(0.8225, abs=5e-5) and r["rbe_variante_occupant"] == pytest.approx(40_953.96, abs=0.01)
87+ assert r["rbe_par_logement"] == pytest.approx(13_525.68, abs=0.01)
88+
89+
90+# ----------------------------------------------------------------------------- SCHL (réel) : lectures des tableaux
91+def test_schl_lectures(res):
92+ s = res["schl_rmr"]
93+ attendu = {"Montréal": (2.1, 2.9, 11.1, 9.4), "Québec": (0.9, 2.4, 13.9, 13.0), "Gatineau (partie QC)": (1.9, 3.8, 17.3, 16.2),
94+ "Sherbrooke": (1.4, 2.7, 10.4, 12.0), "Trois-Rivières": (0.9, 2.7, 10.2, 10.1), "Saguenay": (1.6, 1.3, 13.3, 12.6)}
95+ for k, (i24, i25, r24, r25) in attendu.items():
96+ assert (s.loc[k, "inocc_2024"], s.loc[k, "inocc_2025"], s.loc[k, "rotation_2024"], s.loc[k, "rotation_2025"]) == pytest.approx((i24, i25, r24, r25), abs=0.05)
97+ t = res["schl_taille"].set_index(["zone", "taille"])
98+ assert t.loc[("Île de Montréal", "6 à 19 unités"), "inocc_2025"] == pytest.approx(2.2) and t.loc[("Île de Montréal", "3 à 5 unités"), "inocc_2025"] == pytest.approx(2.3)
99+ assert t.loc[("Île de Montréal", "100 unités et +"), "inocc_2025"] == pytest.approx(5.1) and t.loc[("Île de Montréal", "Tous les immeubles"), "inocc_2025"] == pytest.approx(3.1)
100+ assert t.loc[("RMR de Montréal", "6 à 19 unités"), "rotation_2025"] == pytest.approx(8.5) and t.loc[("RMR de Montréal", "100 unités et +"), "rotation_2025"] == pytest.approx(14.2)
101+ assert t.loc[("RMR de Montréal", "Tous les immeubles"), "inocc_2025"] == pytest.approx(2.9)
102+ q = res["schl_quartiles"]
103+ assert (q.loc["Q1", "inocc_2025"], q.loc["Q4", "inocc_2025"]) == pytest.approx((1.3, 4.9))
104+ io = res["schl_inocc_occ"]
105+ assert (io.loc["Tous", "inoccupes"], io.loc["Tous", "occupes"]) == (1573, 1275) and io.loc["Tous", "ecart_pct"] == pytest.approx(23.4, abs=0.05)
106+ assert (io.loc["2 chambres", "inoccupes"], io.loc["2 chambres", "occupes"]) == (1815, 1349)
107+ a = res["schl_annee"]
108+ assert a.loc["2015 ou après", "inocc_2025"] == pytest.approx(5.6) and a.loc["Avant 1940", "inocc_2025"] == pytest.approx(2.2)
109+
110+
111+def test_ipc_loyer(res):
112+ i = res["ipc_loyer"]
113+ assert (i["sept2026"], i["sept2025"]) == pytest.approx((174.2, 168.6))
114+ assert i["var_12m"] == pytest.approx(0.0332, abs=5e-5) and i["cagr_10ans"] == pytest.approx(0.0402, abs=5e-5)
115+
116+
117+# ----------------------------------------------------------------------------- modèle frictionnel, sensibilité, trajectoires
118+def test_grille_et_sensibilite(res):
119+ g = res["grille_frictionnelle"]
120+ assert g.loc[9.4, 1.0] == pytest.approx(0.78, abs=0.005) and g.loc[13.0, 2.0] == pytest.approx(2.17, abs=0.005) and g.loc[15.0, 1.0] == pytest.approx(1.25)
121+ s = res["sensibilite_v"]; b = res["cas3"]
122+ assert s.attrs["R"] == pytest.approx(0.04415, abs=1e-6)
123+ assert s.attrs["dRBE_dv_pt"] == pytest.approx(-1_655.40, abs=0.01) and s.attrs["dRNE_dv_pt"] == pytest.approx(-1_589.18, abs=0.01)
124+ assert s.attrs["dV_dv_pt"] == pytest.approx(-35_995.11, abs=0.01)
125+ row = s.set_index("v")
126+ assert row.loc[0.039, "valeur"] == pytest.approx(2_037_502.89, abs=0.01) # = ch. 29
127+ assert row.loc[0.029, "valeur"] == pytest.approx(2_073_497.99, abs=0.01) and row.loc[0.08, "valeur"] == pytest.approx(1_889_922.94, abs=0.01)
128+ assert row.loc[0.02, "rbe"] == pytest.approx(171_589.20, abs=0.01) and row.loc[0.06, "rne"] == pytest.approx(86_618.47, abs=0.01)
129+ # linéarité exacte en v : V(v) = V(0,039) + (v − 0,039)·100·dV/dv
130+ for v in (0.02, 0.05, 0.08):
131+ assert row.loc[v, "valeur"] == pytest.approx(row.loc[0.039, "valeur"] + (v - 0.039) * 100 * s.attrs["dV_dv_pt"], abs=0.01)
132+
133+
134+def test_trajectoires(res):
135+ tr = res["trajectoires"]; t = tr["table"].set_index("annee"); h = tr["hyp"]
136+ assert t.loc[1, "base_rot15"] == pytest.approx(165_540) and t.loc[1, "marche"] == pytest.approx(186_036)
137+ # indexation seule = RBP × 1,031^(t−1)
138+ for k in range(1, 12):
139+ assert t.loc[k, "tal_seul"] == pytest.approx(165_540 * 1.031 ** (k - 1), rel=1e-9)
140+ # ordre des trajectoires : TAL seul ≤ rotation 9,4 % ≤ rotation 15 % ≤ marché
141+ assert ((t["tal_seul"] <= t["rot_schl_094"] + 1e-6) & (t["rot_schl_094"] <= t["base_rot15"] + 1e-6) & (t["base_rot15"] <= t["marche"] + 1e-6)).all()
142+ # base = traite.cas3.projection_dcf (ch. 30/47)
143+ p = cas3.tout()["projection"]
144+ assert np.allclose(t["base_rot15"].values, p["rbp"].values) and np.allclose(t["marche"].values, p["rbp_marche"].values)
145+ assert tr["rbp_T"]["tal_seul"] == pytest.approx(217_886.81, abs=0.01) and tr["rbp_T"]["base_rot15"] == pytest.approx(251_101.39, abs=0.01)
146+ assert tr["rbp_T"]["rot_schl_094"] == pytest.approx(247_211.89, abs=0.01) and tr["rbp_T"]["marche"] == pytest.approx(253_547.97, abs=0.01)
147+ assert tr["cagr"]["base_rot15"] == pytest.approx(0.04738, abs=5e-6) and tr["cagr"]["rot_schl_094"] == pytest.approx(0.04557, abs=5e-6)
148+ assert t.loc[10, "ecart_base_pct"] == pytest.approx(0.96, abs=0.005) and t.loc[10, "ecart_tal_pct"] == pytest.approx(14.06, abs=0.005)
149+ assert t.loc[10, "ecart_rot094_pct"] == pytest.approx(2.50, abs=0.005) and t.loc[2, "base_rot15"] == pytest.approx(177_389.21, abs=0.01)
150+ assert tr["valeur_dcf"]["base_rot15"] == pytest.approx(2_208_565.91, abs=0.01) # = ch. 30/47
151+ assert tr["valeur_dcf"]["tal_seul"] == pytest.approx(1_756_414.23, abs=0.01) and tr["valeur_dcf"]["rot_schl_094"] == pytest.approx(2_140_916.43, abs=0.01)
152+ assert tr["somme_rbp_1_T"]["base_rot15"] == pytest.approx(2_113_053.32, abs=0.01)
153+
154+
155+def test_mini_projection():
156+ mp = c.mini_projection([900, 950, 1000, 1100], 1200, 0.031, 0.035, 1, 4)
157+ assert mp.loc[0, "rbp"] == pytest.approx(47_400) and mp.loc[0, "rbp_marche"] == pytest.approx(57_600)
158+ assert mp.loc[1, "rbp"] == pytest.approx(52_638.60, abs=0.01) # 900 → 1 242 ; autres × 1,031
159+ assert mp.loc[2, "rbp"] == pytest.approx(57_578.28, abs=0.01) and mp.loc[3, "rbp"] == pytest.approx(62_177.79, abs=0.01)
160+ assert (mp["rbp"] <= mp["rbp_marche"] + 1e-9).all()
161+
162+
163+# ----------------------------------------------------------------------------- exercices (solutions/ch27.tex)
164+def test_exercices(res):
165+ ex = res["exercices"]
166+ assert ex["1"]["rbp_c"] == 78_360 and ex["1"]["rbp_m"] == 87_000 and ex["1"]["deficit"] == 8_640 and ex["1"]["deficit_pct"] == pytest.approx(0.0993, abs=5e-5)
167+ assert ex["2"]["rbe_livre"] == pytest.approx(80_025.60, abs=0.01) and ex["2"]["rbe_v_sur_tout"] == pytest.approx(79_833.60, abs=0.01)
168+ assert ex["2"]["rbe_autres_demi"] == pytest.approx(79_929.60, abs=0.01) and ex["2"]["vacance"] == pytest.approx(3_134.40, abs=0.01)
169+ assert ex["3"]["vf_1"] == pytest.approx(0.010833, abs=1e-6) and ex["3"]["vf_15"] == pytest.approx(0.01625, abs=1e-9)
170+ assert ex["3"]["v_1"] == pytest.approx(0.015833, abs=1e-6) and ex["3"]["v_15"] == pytest.approx(0.02125, abs=1e-9) and ex["3"]["duree_implicite_schl"] == pytest.approx(2.215, abs=5e-4)
171+ assert ex["4"]["L_eff_1_12"] == pytest.approx(1_191.67, abs=0.01) and ex["4"]["rbe_correct"] == pytest.approx(14_300) and ex["4"]["double_compte"] == pytest.approx(13_108.33, abs=0.01)
172+ assert ex["5"]["rbe"] == pytest.approx(40_577.04, abs=0.01) and ex["5"]["rbe_variante_occupant"] == pytest.approx(40_953.96, abs=0.01)
173+ assert ex["6"]["rbp"] == pytest.approx([47_400, 52_638.60, 57_578.28, 62_177.79], abs=0.01)
174+ assert ex["6"]["tal_seul"][3] == pytest.approx(51_946.27, abs=0.01) and ex["6"]["ecart_pct"][3] == pytest.approx(0.0264, abs=5e-5)
175+ assert ex["7"]["V_029"] == pytest.approx(2_073_497.99, abs=0.01) and ex["7"]["V_059"] == pytest.approx(1_965_512.67, abs=0.01)
176+ assert ex["7"]["rne_029"] == pytest.approx(91_544.94, abs=0.01) and ex["7"]["rne_059"] == pytest.approx(86_777.38, abs=0.01)
177+ assert ex["7"]["approx_lineaire_2pts"] == pytest.approx(ex["7"]["V_059"] - ex["7"]["V_039"], abs=0.01) # linéaire en v
178+ assert ex["8"]["ratio_tous"] == pytest.approx(1.2337, abs=5e-5) and ex["8"]["ratio_2ch"] == pytest.approx(1.3454, abs=5e-5) and ex["8"]["q4_sur_q1"] == pytest.approx(3.77, abs=0.005)
179+
180+
181+def test_figures_presentes():
182+ for slug in ("inoccupation-rmr", "cascade", "trajectoires", "sensibilite"):
183+ assert (ROOT / f"assets/figures/ch27/ch27-fig-{slug}.pdf").exists(), slug
added analysis/python/tests/test_ch32.py +132 −0
@@ -0,0 +1,132 @@
1+"""Tests du chapitre 32 — Duplex à quintuplex : identités, cohérence avec traite.cas2 (partagé avec les ch. 26, 27, 46),
2+valeurs citées dans le texte (tolérances déclarées), règles de financement vérifiées, exercices, présence des figures."""
3+import pathlib, sys
4+import numpy as np, pytest
5+
6+ROOT = pathlib.Path(__file__).resolve().parents[3]
7+sys.path.insert(0, str(ROOT / "analysis/python/cases"))
8+import ch32_plex as C # noqa: E402
9+from traite import cas2, finance as fin # noqa: E402
10+
11+
12+@pytest.fixture(scope="module")
13+def R():
14+ return C.calculer()
15+
16+
17+# ----------------------------------------------------------------------------- cohérence avec le module partagé
18+def test_coherence_cas2_partage(R):
19+ r = cas2.tout(); b = R["base"]
20+ assert b["rbp_contrat"] == pytest.approx(41832.0) and b["rbp_marche"] == pytest.approx(49332.0)
21+ assert b["rne"] == pytest.approx(22033.9584, abs=0.01) and b["ratio_depenses"] == pytest.approx(0.457, abs=0.001)
22+ assert R["indicateurs"] == r["indicateurs"]
23+ assert R["indications"]["v_mrb_127"] == pytest.approx(r["valeur_mrb"], abs=0.01)
24+ assert R["indications"]["v_cap_127"] == pytest.approx(r["valeur_capitalisation"], abs=0.01)
25+ assert R["indications"]["v_log_127"] == pytest.approx(r["valeur_par_logement"], abs=0.01)
26+
27+
28+def test_valeurs_citees_indicateurs(R):
29+ ind = R["indicateurs"]
30+ assert ind["n"] == 127 and ind["n_triplex"] == 67
31+ assert ind["mrb_median_triplex"] == pytest.approx(19.82, abs=0.005) and ind["taux_median"] == pytest.approx(0.0317, abs=5e-5)
32+ assert ind["prix_par_logement_triplex"] == pytest.approx(281000, abs=1)
33+ assert R["indications"]["v_mrb_127"] == pytest.approx(829110.24, abs=1)
34+ assert R["indications"]["v_cap_127"] == pytest.approx(695077.55, abs=1)
35+ assert R["indications"]["v_log_127"] == pytest.approx(843000, abs=1)
36+ pt = R["par_taille"]; assert pt.loc[2, "n"] == 60 and pt.loc[3, "n"] == 67
37+ assert pt.loc[3, "prix_median"] == pytest.approx(843000, abs=1) and pt.loc[2, "prix_median"] == pytest.approx(583500, abs=1)
38+
39+
40+# ----------------------------------------------------------------------------- identités
41+def test_identite_mrb_taux(R):
42+ """R = (1 − v)(1 − d)/MRB pour chaque vente du jeu (v = 2,4 %, d = ratio de la vente), aux arrondis du fichier près."""
43+ g = R["decomposition"]
44+ lhs = g["taux_global"]; rhs = (1 - 0.024) * (1 - g["ratio_depenses"]) / g["mrb"]
45+ assert np.max(np.abs(lhs - rhs)) < 2e-4
46+
47+
48+def test_decomposition_rho(R):
49+ """R_c = ρ R_m lorsque les dépenses sont proportionnelles au revenu (jeu synthétique)."""
50+ g = R["decomposition"]
51+ assert np.max(np.abs(g["taux_contrat_reconstitue"] - g["taux_global"])) < 2e-4
52+ assert g["taux_marche"].median() > g["taux_global"].median()
53+ assert R["regression"]["b_rho"] > 0 and 0.03 < R["regression"]["a"] < 0.04
54+
55+
56+def test_valeurs_citees_regression_et_indications(R):
57+ reg = R["regression"]; I = R["indications"]
58+ assert reg["a"] == pytest.approx(0.0345, abs=1e-4) and reg["b_rho"] == pytest.approx(0.0284, abs=1e-4)
59+ assert I["rho_sujet"] == pytest.approx(0.848, abs=5e-4)
60+ assert I["taux_ajuste"] == pytest.approx(0.0302, abs=1e-4)
61+ assert I["v_cap_ajuste"] == pytest.approx(729549, abs=1)
62+ assert I["v_log_comp"] == pytest.approx(918000, abs=1) and I["v_mrb_comp"] == pytest.approx(922396, abs=1)
63+ assert I["v_cap_comp"] == pytest.approx(741884, abs=1)
64+ assert I["valeur_ponderee"] == pytest.approx(872426, abs=1) and I["valeur_arrondie"] == 870000
65+ assert I["valeur_ponderee"] == pytest.approx(sum(C.POIDS_INDICATIONS[k] * v for k, v in I["retenues"].items()))
66+ assert abs(sum(C.POIDS_INDICATIONS.values()) - 1) < 1e-12
67+ assert I["v_cap_ratio_ventes"] == pytest.approx(863880, abs=1) and I["v_log_127_indexe"] == pytest.approx(900926, abs=1)
68+
69+
70+def test_comparables_selection(R):
71+ comp5 = R["comparables5"]
72+ assert len(comp5) == 5 and set(comp5["id_vente"]) == {"Q0187", "Q0222", "Q0243", "Q0207", "Q0235"}
73+ assert (comp5["quartier"] == "Limoilou").all() and (comp5["nb_logements"] == 3).all()
74+ assert comp5["distance"].is_monotonic_increasing and len(R["candidats"]) == 28
75+ assert comp5["prix_par_logement"].median() == pytest.approx(306000, abs=1) and comp5["mrb"].median() == pytest.approx(22.05, abs=0.005)
76+
77+
78+def test_prime_occupant(R):
79+ p = R["prime"]; I = R["indications"]
80+ assert p["v_investisseur_contrat"] + p["v_rattrapage"] + p["prime"] == pytest.approx(I["valeur_ponderee"])
81+ assert p["v_investisseur_contrat"] == pytest.approx(440679.17, abs=0.01) and p["prime_pct"] == pytest.approx(0.335, abs=0.001)
82+
83+
84+# ----------------------------------------------------------------------------- financement (règles vérifiées, notes-C §4.3, §4.2)
85+def test_mise_de_fonds_minimale():
86+ assert C.mise_de_fonds_minimale_assuree(450_000) == pytest.approx(22_500)
87+ assert C.mise_de_fonds_minimale_assuree(843_000) == pytest.approx(59_300)
88+ assert C.mise_de_fonds_minimale_assuree(870_000) == pytest.approx(62_000)
89+ assert C.mise_de_fonds_minimale_assuree(1_500_000) == pytest.approx(125_000)
90+ assert np.isnan(C.mise_de_fonds_minimale_assuree(1_600_000))
91+
92+
93+def test_financement_occupant(R):
94+ f = R["financement"]
95+ assert f["pret"] == pytest.approx(808_000) and f["rpv"] == pytest.approx(0.9287, abs=1e-4)
96+ assert f["paiement_mensuel"] == pytest.approx(4302.97, abs=0.01) and f["service_dette"] == pytest.approx(51635.62, abs=0.01)
97+ # composition semestrielle : taux mensuel équivalent (1 + 0,0412/2)^(1/6) − 1
98+ i_m = (1 + 0.0412 / 2) ** (1 / 6) - 1
99+ assert f["paiement_mensuel"] == pytest.approx(808_000 * i_m / (1 - (1 + i_m) ** -300), abs=0.01)
100+ assert f["taux_admissible"] == pytest.approx(0.0612) and f["paiement_mensuel_tam"] == pytest.approx(5227.41, abs=0.01)
101+ h = fin.Hypotheque(f["pret"], f["taux"], f["amort_mois"]); assert h.solde(300) == pytest.approx(0.0, abs=1e-6)
102+ o = R["occupation"]
103+ assert o["cout_net_mensuel"] == pytest.approx(3532.11, abs=0.01) and o["cout_net_mensuel_hors_capital"] == pytest.approx(1813.08, abs=0.01)
104+ assert o["cout_net_annuel"] == pytest.approx(o["sortie_brute"] - o["loyers_percus_nets"])
105+
106+
107+def test_sensibilite(R):
108+ S = R["sensibilite"]; L = S["loyers"]
109+ assert L["v_cap_ajuste"].is_monotonic_increasing and L["v_mrb"].is_monotonic_increasing
110+ assert L.loc[L["rho"] == 1.0, "rne"].item() == pytest.approx(R["rne_marche"], abs=0.01)
111+ F = S["financement"]
112+ assert F["cout_net_mensuel"].iloc[4] > F["cout_net_mensuel"].iloc[0] # +100 pb coûte plus cher
113+ assert F["service_dette"].iloc[1] < F["service_dette"].iloc[0] # 30 ans < 25 ans
114+ T = S["taux"]; assert all(T["valeur"] == pytest.approx(R["base"]["rne"] / T["taux"]))
115+
116+
117+# ----------------------------------------------------------------------------- exercices (solutions/ch32.tex)
118+def test_exercices():
119+ E = C.exercices()
120+ assert E["1"]["mrb"] == pytest.approx(18.75) and E["1"]["taux"] == pytest.approx(E["1"]["identite"])
121+ assert E["1"]["rne"] == pytest.approx(18763.68)
122+ assert E["2"]["843000.0"]["mise"] == pytest.approx(59300) and E["2"]["450000.0"]["mise"] == pytest.approx(22500)
123+ e3 = E["3"]; assert e3["R_m"] - e3["R_c"] == pytest.approx(e3["terme"]) and e3["ecart_pb"] == pytest.approx(105.36, abs=0.01)
124+ assert E["4"]["2e"]["cout_net_mensuel"] > E["4"]["RC"]["cout_net_mensuel"]
125+ assert E["6"]["ponderations"]["comparaison seule (50/50/0)"] == pytest.approx(920197.8, abs=0.1)
126+ assert E["6"]["ponderations"]["revenu dominant (20/20/60)"] == pytest.approx(805809, abs=1)
127+ assert E["7"]["va_1an"] == pytest.approx(4335.85, abs=0.01) and E["7"]["va_10ans"] == pytest.approx(40791.47, abs=0.01)
128+
129+
130+def test_figures_presentes():
131+ for slug in ("mrb-taille-quartier", "taux-loyer-relatif", "prix-logement-quartier", "plex-apciq-rmr", "sensibilite-occupant"):
132+ assert (ROOT / f"assets/figures/ch32/ch32-fig-{slug}.pdf").exists(), slug
modified assets/figures/ch25/ch25-fig-carte-terrains.pdf +0 −0

Binary file not shown.

modified assets/figures/ch25/ch25-fig-lotissement.pdf +0 −0

Binary file not shown.

modified assets/figures/ch25/ch25-fig-residuelle-sensibilite.pdf +0 −0

Binary file not shown.

modified assets/figures/ch25/ch25-fig-valeur-unitaire-superficie.pdf +0 −0

Binary file not shown.

modified assets/figures/ch26/ch26-fig-anciennete.pdf +0 −0

Binary file not shown.

modified assets/figures/ch26/ch26-fig-contrat-marche.pdf +0 −0

Binary file not shown.

modified assets/figures/ch26/ch26-fig-normalisation.pdf +0 −0

Binary file not shown.

modified assets/figures/ch26/ch26-fig-va-ecart.pdf +0 −0

Binary file not shown.

modified assets/figures/ch27/ch27-fig-cascade.pdf +0 −0

Binary file not shown.

modified assets/figures/ch27/ch27-fig-inoccupation-rmr.pdf +0 −0

Binary file not shown.

modified assets/figures/ch27/ch27-fig-sensibilite.pdf +0 −0

Binary file not shown.

modified assets/figures/ch27/ch27-fig-trajectoires.pdf +0 −0

Binary file not shown.

added assets/figures/ch28/ch28-fig-cascade.pdf +0 −0

Binary file not shown.

added assets/figures/ch28/ch28-fig-evolution.pdf +0 −0

Binary file not shown.

added assets/figures/ch28/ch28-fig-ratio-loyer.pdf +0 −0

Binary file not shown.

added assets/figures/ch28/ch28-fig-sensibilite.pdf +0 −0

Binary file not shown.

added assets/figures/ch28/ch28-fig-structure.pdf +0 −0

Binary file not shown.

added assets/figures/ch32/ch32-fig-mrb-taille-quartier.pdf +0 −0

Binary file not shown.

added assets/figures/ch32/ch32-fig-plex-apciq-rmr.pdf +0 −0

Binary file not shown.

added assets/figures/ch32/ch32-fig-prix-logement-quartier.pdf +0 −0

Binary file not shown.

added assets/figures/ch32/ch32-fig-sensibilite-occupant.pdf +0 −0

Binary file not shown.

added assets/figures/ch32/ch32-fig-taux-loyer-relatif.pdf +0 −0

Binary file not shown.

modified data/synthetic/README.md +3 −0
@@ -67,3 +67,6 @@ Les relations entre variables (élasticités, primes de secteur, décote d'ancie
67 67
68 ## Chapitre 8 — « Val-Fictif » (zonage) — ajouté le 2026-10-31 68 ## Chapitre 8 — « Val-Fictif » (zonage) — ajouté le 2026-10-31
69 `ch08_zonage_lots.csv` (400 lots d'une grille 20 × 20, cinq zones H1/H2/H3/C/P, îlot de 36 lots rezoné H1 → H2 le 2025-01-01) et `ch08_zonage_ventes.csv` (482 ventes résidentielles 2023-01 → 2026-08) sont générés par `analysis/python/cases/ch08_zonage.py` (graine 20261031). Loi : ln P = ln(prix de base de la zone) + 0,50 ln(S_hab/S_type) + 0,15 ln(S_terr/600) − 0,004·âge + ln(1,03)^(années depuis 2024-07-01) + prime de rezonage [0,03 + 0,0015·max(âge − 20 ; 0)] pour les ventes de l'îlot postérieures au rezonage + ε, σ = 0,07 ; prix arrondis à 500 $. Municipalité, zones, lots et prix sont entièrement fictifs (aucune calibration sur une municipalité réelle ; ordres de grandeur du cas 1 repris pour la zone H1). 69 `ch08_zonage_lots.csv` (400 lots d'une grille 20 × 20, cinq zones H1/H2/H3/C/P, îlot de 36 lots rezoné H1 → H2 le 2025-01-01) et `ch08_zonage_ventes.csv` (482 ventes résidentielles 2023-01 → 2026-08) sont générés par `analysis/python/cases/ch08_zonage.py` (graine 20261031). Loi : ln P = ln(prix de base de la zone) + 0,50 ln(S_hab/S_type) + 0,15 ln(S_terr/600) − 0,004·âge + ln(1,03)^(années depuis 2024-07-01) + prime de rezonage [0,03 + 0,0015·max(âge − 20 ; 0)] pour les ventes de l'îlot postérieures au rezonage + ε, σ = 0,07 ; prix arrondis à 500 $. Municipalité, zones, lots et prix sont entièrement fictifs (aucune calibration sur une municipalité réelle ; ordres de grandeur du cas 1 repris pour la zone H1).
70+
71+## Chapitre 25 — ventes de terrains vacants « Gatineau » — ajouté le 2026-10-31
72+`ch25_terrains_ventes.csv` (200 ventes de terrains vacants résidentiels, 2023-01 → 2026-08, cinq secteurs : Hull 70, Gatineau 60, Aylmer 40, Buckingham 15, Masson-Angers 15 ; 156 desservis) est généré par `analysis/python/cases/ch25_terrain.py` (`generer_terrains`, graine 20261031). Variables : superficie (m²), façade (m), desservi (aqueduc et égout, 0/1), topographie (plat/pente/roc), forme (régulier/irrégulier), logements constructibles (1 à 3), coordonnées fictives, prix. Loi : ln P = ln(225 000) + 0,50 ln(T/560) + 0,08 ln(F/18) − 0,30·(non desservi) − 0,06·pente − 0,12·roc − 0,05·irrégulier + 0,12·(logements − 1) + prime de secteur (log du rapport des médianes APCIQ T3 2026 à celle de Hull, comme le cas 1) + 0,0036·mois depuis 2024-07-01 + ε, σ = 0,10 ; prix arrondis à 500 $. Le prix de référence de 225 000 $ (terrain de 560 m², desservi, plat, régulier, Hull, juillet 2024) est une **hypothèse pédagogique** choisie pour la cohérence interne avec l'extraction terrain/bâtiment du cas 1 (part du terrain ≈ 45-50 % dans les ventes synthétiques de Hull) ; **aucune vente réelle de terrain à Gatineau n'a servi à la calibration** (la grille de terrains de l'auteur n'est pas dans ce dépôt). Les élasticités et décotes sont des hypothèses vraisemblables, non des estimations.
added manuscript/_wrap_ch19.tex +12 −0
@@ -0,0 +1,12 @@
1+\providecommand{\BUILDVARIANT}{uqo}\providecommand{\BUILDMEDIA}{numerique}
2+\documentclass[10pt,twoside,openright]{book}
3+\newif\ifLogoAutorise\LogoAutorisefalse\def\PALETTE{uqo}
4+\usepackage{traite-qc}
5+\input{../manuscript/frontmatter/metadata.tex}
6+\input{/Users/simon-pierreboucher/Desktop/UQO/Traite-Evaluation-QC/build/ch19/_labels.tex}
7+\begin{document}
8+\setcounter{chapter}{18}
9+\input{chapters/ch19.tex}
10+\appendix\chapter{Solutions du chapitre}\input{../solutions/ch19.tex}
11+\printbibliography
12+\end{document}
modified manuscript/chapters/ch11.tex +2 −2
@@ -20,7 +20,7 @@ Aucune méthode d'évaluation ne vaut mieux que la connaissance que l'évaluateu
20 20
21 Les Normes de pratique professionnelle de l'Ordre des évaluateurs agréés du Québec posent l'exigence en termes simples : tout immeuble évalué doit faire l'objet d'une inspection, sauf circonstances exceptionnelles qui seront expliquées au rapport \parencite[règle 2.4]{oeaqNormes}. La règle est coercitive, et elle se comprend à la lumière du contenu minimal du rapport : le premier des douze éléments exigés est l'identification physique et légale de l'immeuble, le sixième l'étendue du travail, le septième les réserves et hypothèses de base, le huitième le traitement de l'information et le raisonnement \parencite[règle 2.3]{oeaqNormes}. L'inspection est ce qui donne un contenu à ces éléments. Sans elle, la description du bâtiment est empruntée au rôle d'évaluation, à la fiche de courtage ou aux dires du propriétaire, trois sources dont le \cref{ch:13} montre les limites, et la conclusion de valeur repose sur un immeuble supposé plutôt que constaté. 21 Les Normes de pratique professionnelle de l'Ordre des évaluateurs agréés du Québec posent l'exigence en termes simples : tout immeuble évalué doit faire l'objet d'une inspection, sauf circonstances exceptionnelles qui seront expliquées au rapport \parencite[règle 2.4]{oeaqNormes}. La règle est coercitive, et elle se comprend à la lumière du contenu minimal du rapport : le premier des douze éléments exigés est l'identification physique et légale de l'immeuble, le sixième l'étendue du travail, le septième les réserves et hypothèses de base, le huitième le traitement de l'information et le raisonnement \parencite[règle 2.3]{oeaqNormes}. L'inspection est ce qui donne un contenu à ces éléments. Sans elle, la description du bâtiment est empruntée au rôle d'évaluation, à la fiche de courtage ou aux dires du propriétaire, trois sources dont le \cref{ch:13} montre les limites, et la conclusion de valeur repose sur un immeuble supposé plutôt que constaté.
22 22
23−L'\motcle{inspection d'évaluation} a donc un objet précis : établir, à la date d'évaluation, les caractéristiques physiques du bien qui influent sur sa valeur, et parmi elles l'état de chaque composante, les réparations différées, les améliorations récentes, les éléments fonctionnellement désuets et les indices de pathologies cachées. Elle ne cherche ni à certifier que le bâtiment est sain ni à garantir l'absence de vices : elle cherche à savoir ce qu'un acheteur prudent et informé constaterait, puisque c'est de ce point de vue que la valeur marchande est définie (\cref{ch:2}). Lorsqu'un indice sérieux échappe à l'observation directe, l'évaluateur ne tranche pas ; il formule une \motcle{hypothèse extraordinaire}, en indique l'incidence sur la valeur et recommande, s'il y a lieu, l'intervention d'un spécialiste \parencite[règle 2.1~c)]{oeaqNormes}. La \cref{sec:11:information} donne à cette recommandation une forme quantitative. 23+L'\motcle{inspection d'évaluation} a donc un objet précis : établir, à la date d'évaluation, les caractéristiques physiques du bien qui influent sur sa valeur, et parmi elles l'état de chaque composante, les réparations différées, les améliorations récentes, les éléments fonctionnellement désuets et les indices de pathologies cachées. Elle ne cherche ni à certifier que le bâtiment est sain ni à garantir l'absence de vices : elle cherche à savoir ce qu'un acheteur prudent et informé constaterait, puisque c'est de ce point de vue que la valeur marchande est définie (\cref{ch:02}). Lorsqu'un indice sérieux échappe à l'observation directe, l'évaluateur ne tranche pas ; il formule une \motcle{hypothèse extraordinaire}, en indique l'incidence sur la valeur et recommande, s'il y a lieu, l'intervention d'un spécialiste \parencite[règle 2.1~c)]{oeaqNormes}. La \cref{sec:11:information} donne à cette recommandation une forme quantitative.
24 24
25 \subsection{Inspection d'évaluation et inspection préachat} 25 \subsection{Inspection d'évaluation et inspection préachat}
26 26
@@ -421,7 +421,7 @@ Total $D_{\mathrm{cur}}$ (2,64 \% du coût neuf) & & & 10479.98 \\
421 \caption{Dépréciation physique ventilée du sujet du cas 1 et comparaison avec l'âge-vie simple (données synthétiques)}\label{tab:11:depreciation} 421 \caption{Dépréciation physique ventilée du sujet du cas 1 et comparaison avec l'âge-vie simple (données synthétiques)}\label{tab:11:depreciation}
422 \centering 422 \centering
423 \small 423 \small
424−\begin{tabular}{lS[table-format=6.2]S[table-format=2.2]} 424+\begin{tabular}{lS[table-format=6.2]S[table-format=3.2]}
425 \toprule 425 \toprule
426 Élément & {Montant (\$)} & {\% de $C_N$} \\ 426 Élément & {Montant (\$)} & {\% de $C_N$} \\
427 \midrule 427 \midrule
modified manuscript/chapters/ch13.tex +587 −1
@@ -1,2 +1,588 @@
1 \chapter{Transactions, inscriptions, loyers et coûts}\label{ch:13} 1 \chapter{Transactions, inscriptions, loyers et coûts}\label{ch:13}
2−\stubchapter 2+
3+Aucune des méthodes exposées dans cet ouvrage ne vaut mieux que les données qui l'alimentent. Un comparable mal daté, un loyer d'annonce pris pour un loyer payé, un indice de prix confondu avec un indice de coût ou une vente entre parents traitée comme une vente sincère suffisent à fausser une conclusion de valeur, quelle que soit la sophistication du modèle qui la produit. Le présent chapitre dresse l'inventaire raisonné des sources de données dont dispose l'évaluateur au Québec en 2026, en distinguant systématiquement la donnée officielle, produite par un organisme public dans l'exercice d'une mission légale, de la donnée d'industrie, produite par des acteurs du marché pour leurs propres besoins et diffusée à leurs conditions. Il explique ce que chaque source contient, ce qu'elle ne contient pas, à quel prix et sous quelle licence on y accède, puis il détaille les pièges propres aux quatre grandes familles de données que l'évaluateur combine dans chaque dossier : les transactions, les inscriptions au registre foncier et au rôle, les loyers et les coûts de construction. Il se termine par la description d'une chaîne de données reproductible, depuis l'acquisition jusqu'à la base d'évaluation, et par un exemple intégral qui lit, par script, des fichiers réels déposés dans le dépôt de l'ouvrage. Le \cref{ch:14} prolonge ce travail par le nettoyage, le géocodage et la mesure de la qualité ; le \cref{ch:15} exploite la base ainsi constituée pour l'étude de marché.
4+
5+\begin{objectifs}
6+À la fin de ce chapitre, le lecteur sait :
7+\begin{itemize}
8+\item dresser l'inventaire des sources de données immobilières disponibles au Québec, en classant chacune selon sa nature (officielle ou d'industrie), son contenu, sa couverture, sa périodicité, son mode d'accès, sa licence et son coût ;
9+\item lire un acte de vente, une fiche de courtage et une inscription au rôle d'évaluation comme des enregistrements de données, en identifiant les champs qui informent sur la valeur et ceux qui la brouillent ;
10+\item reconnaître et traiter les transactions non représentatives (ventes liées, successions, reprises de finance, ventes de promoteur, meubles inclus, prix déclaré) et corriger la date d'une vente ;
11+\item interpréter les données de loyers de la Société canadienne d'hypothèques et de logement et du Tribunal administratif du logement en connaissant leur univers, leur méthode et leurs limites ;
12+\item distinguer un indice de coût de construction d'un indice de prix et d'un indice des prix à la consommation, et lire le fichier brut d'un tableau de Statistique Canada ;
13+\item concevoir une base d'évaluation documentée : schéma de la chaîne de données, clés d'appariement, journal des exclusions, métadonnées de licence et de date.
14+\end{itemize}
15+\end{objectifs}
16+
17+\section{Panorama des sources de données au Québec}\label{sec:13:panorama}
18+
19+\subsection{Trois familles de sources}
20+
21+Les données qu'utilise l'évaluateur résidentiel québécois proviennent de trois familles d'institutions dont les logiques diffèrent, et cette différence d'origine explique la plupart des écarts qu'on observe entre elles. La première famille est celle des \motcle{registres administratifs} : le registre foncier, tenu par l'Officier de la publicité foncière en vertu du Code civil du Québec, le rôle d'évaluation foncière, dressé par les organismes municipaux responsables de l'évaluation en vertu de la Loi sur la fiscalité municipale, et les registres de permis des municipalités. Ces registres n'ont pas été conçus pour l'analyse du marché ; ils existent pour rendre les droits opposables aux tiers, pour asseoir la taxation ou pour contrôler la construction. Leur exhaustivité est leur grande qualité, puisque toute vente d'immeuble qui veut être opposable aux tiers doit être publiée \parencite[art.~2934 et 2941]{ccq} et que toute unité d'évaluation doit figurer au rôle \parencite[art.~42]{lfm} ; leur limite est que les champs qu'ils contiennent sont ceux que la loi prescrit, ni plus ni moins, et que la vérification de leur contenu obéit à des finalités juridiques et fiscales, non à celles de l'analyste.
22+
23+La deuxième famille est celle des \motcle{enquêtes et statistiques officielles} : Statistique Canada, la Société canadienne d'hypothèques et de logement (SCHL), la Banque du Canada, l'Institut de la statistique du Québec et, pour les loyers, le Tribunal administratif du logement (TAL). Ces organismes produisent des agrégats, des indices et des taux selon des méthodologies publiées, avec des indicateurs de qualité et des calendriers de diffusion connus. Leur force est la rigueur méthodologique et la stabilité des définitions ; leur limite, du point de vue de l'évaluateur, est qu'ils décrivent des marchés et non des immeubles : on y trouve le loyer moyen d'une zone, pas le loyer d'un logement, l'indice des prix de la construction d'une région métropolitaine, pas le coût d'un bâtiment.
24+
25+La troisième famille est celle des \motcle{données d'industrie} : les statistiques de ventes de l'Association professionnelle des courtiers immobiliers du Québec (APCIQ) tirées du système Centris, les compilations du registre foncier commercialisées par JLR, l'indice Teranet--Banque Nationale, les taux de capitalisation publiés par les firmes de courtage commercial, les guides de coûts de construction et les enquêtes des assureurs. Ces données sont souvent les plus proches de ce dont l'évaluateur a besoin, parce qu'elles ont été construites par des praticiens pour des praticiens, mais elles sont produites à des fins commerciales, selon des méthodes qui ne sont pas toujours documentées, sur des univers qui ne couvrent pas tout le marché, et elles sont protégées par le droit d'auteur et des conditions d'utilisation qu'il faut respecter.
26+
27+\begin{definition}{Donnée officielle et donnée d'industrie}{officielle}
28+Une \emph{donnée officielle} est produite par un organisme public ou parapublic dans l'exercice d'une mission prévue par la loi (registre, rôle, enquête statistique, décision réglementaire), selon une méthodologie publiée, et diffusée sous une licence qui en autorise la réutilisation avec attribution. Une \emph{donnée d'industrie} est produite par un acteur privé du marché à partir de ses propres opérations ou de compilations de sources publiques, selon une méthodologie qui lui appartient, et diffusée à ses conditions. La distinction ne préjuge pas de la qualité : elle détermine ce que l'évaluateur peut savoir de la méthode, ce qu'il peut reproduire, ce qu'il a le droit de citer et ce qu'il doit attribuer.
29+\end{definition}
30+
31+Le \cref{tab:13:inventaire} dresse l'inventaire des sources effectivement vérifiées pour cet ouvrage ; le \cref{tab:13:acces} en donne les conditions d'accès, la licence, le coût et l'adresse consultée. Les deux tableaux se lisent ensemble. La colonne « type » applique la distinction de la \cref{def:officielle} : [O] pour une donnée officielle, [I] pour une donnée d'industrie. Toutes les adresses ont été consultées le 31 octobre 2026 ; la date figure parce qu'une adresse ou une licence peut changer et qu'un rapport d'évaluation doit permettre au lecteur de retrouver ce que l'évaluateur a vu.
32+
33+\begin{footnotesize}
34+\begin{longtable}{@{}L{34mm}L{52mm}L{22mm}c@{}}
35+\caption{Inventaire des sources de données utiles à l'évaluation résidentielle et multirésidentielle au Québec : contenu, couverture et périodicité (état au 31 octobre 2026)}\label{tab:13:inventaire}\\
36+\toprule
37+Source (organisme) & Contenu et couverture & Périodicité & Type \\
38+\midrule
39+\endfirsthead
40+\caption[]{Inventaire des sources (suite)}\\
41+\toprule
42+Source (organisme) & Contenu et couverture & Périodicité & Type \\
43+\midrule
44+\endhead
45+\bottomrule
46+\endfoot
47+\multicolumn{4}{@{}l}{\emph{Registres administratifs}}\\
48+Registre foncier du Québec (ministère des Ressources naturelles et des Forêts, Officier de la publicité foncière) & Actes publiés (ventes, hypothèques, servitudes, avis), index des immeubles par lot, plans cadastraux ; tout le Québec ; registre public & Continu (inscriptions quotidiennes) & [O] \\
49+Rôle d'évaluation foncière en format ouvert (MAMH, Données Québec) & 1~140 fichiers XML annoncés (un par municipalité ; 1~134 lignes dans l'index 2026), unités d'évaluation : adresse, utilisation (CUBF), terrain, année, aire d'étages, logements, valeurs terrain/bâtiment/immeuble, date de référence ; noms, adresses postales et cadastre caviardés & Annuelle (rôles au 1\textsuperscript{er} janvier) ; index trimestriel & [O] \\
50+Unités d'évaluation foncière (Ville de Montréal, données ouvertes) & Agglomération de Montréal : unités, CUBF, logements, année de construction, superficies terrain et bâtiment, matricule ; découpage sans valeur légale & Hebdomadaire & [O] \\
51+Permis de construction, transformation et démolition (Villes de Montréal et de Québec) & Permis délivrés, nature des travaux, coûts déclarés (sans valeur légale à Montréal) ; Lachine et Saint-Léonard temporairement absents du jeu montréalais & Hebdomadaire (Montréal) ; mise à jour du 25 octobre 2026 (Québec) & [O] \\
52+Richesse foncière uniformisée et profil financier des municipalités (MAMH) & Évaluation uniformisée par catégorie d'immeubles et facteur comparatif pour les 1~132 municipalités locales ; finances municipales & Annuelle & [O] \\
53+Manuel d'évaluation foncière du Québec (MAMH), édition 2026 & Méthodes, fichiers permanents (mutations 2A, dossiers de propriété 2C, unités de voisinage 2D), normes de mesurage, barèmes de coûts 3E ; applicable aux rôles entrant en vigueur le 1\textsuperscript{er} janvier 2027 & Annuelle & [O] \\
54+\addlinespace
55+\multicolumn{4}{@{}l}{\emph{Enquêtes et statistiques officielles}}\\
56+SCHL, Enquête sur les logements locatifs (Rapport sur le marché locatif) & Appartements d'initiative privée dans les immeubles de trois logements et plus : univers, taux d'inoccupation, loyers moyens, variation estimative, rotation, par RMR, zone, taille de logement, taille d'immeuble, année de construction ; copropriétés locatives ; six RMR du Québec & Annuelle (enquête d'octobre, publication en décembre) & [O] \\
57+Statistique Canada, tableau 18-10-0289-01 (IPCB) & Indices des prix de la construction de bâtiments, 2023 = 100, 25 géographies (dont Montréal et, depuis 2023, Québec), 16 types d'immeubles, 24 divisions ; remplace le tableau 18-10-0276-01 (archivé) & Trimestrielle & [O] \\
58+Statistique Canada, tableau 18-10-0205-01 (IPLN) & Indice des prix des logements neufs, décembre 2016 = 100, maison seulement, terrain seulement, total ; Montréal, Québec, Sherbrooke, Trois-Rivières, Ottawa-Gatineau (partie Québec), province & Mensuelle & [O] \\
59+Statistique Canada, tableau 18-10-0004-01 (IPC) & Indice des prix à la consommation, 2002 = 100, ensemble et composantes (dont « loyer »), Québec, Montréal, Canada & Mensuelle & [O] \\
60+Statistique Canada, tableaux 34-10-0135-01 et 34-10-0143-01 (mises en chantier, données SCHL) & Logements mis en chantier, en construction et achevés ; province, RMR de Montréal, centres de 10~000 habitants et plus & Trimestrielle et mensuelle & [O] \\
61+Statistique Canada, Recensement de 2021 (tableaux 98-10-0240-01 et 98-10-0231-01) et 17-10-0148-01 & Ménages selon le type de construction et le mode d'occupation ; population des RMR au 1\textsuperscript{er} juillet & Quinquennale ; annuelle & [O] \\
62+Banque du Canada (API Valet) & Taux cible du financement à un jour, rendements des obligations de référence, taux hypothécaires affichés et taux effectifs des fonds avancés & Quotidienne, hebdomadaire, mensuelle & [O] \\
63+Tribunal administratif du logement & Pourcentages applicables aux critères de fixation de loyer, formulaire RN, résumés de décisions ; aucune base de loyers individuels diffusée & Annuelle (janvier) & [O] \\
64+\addlinespace
65+\multicolumn{4}{@{}l}{\emph{Données d'industrie}}\\
66+APCIQ, statistiques Centris (communiqués, Baromètres FSMI) & Ventes conclues par courtier : nombre de ventes, inscriptions en vigueur, prix médians par catégorie (unifamiliale, copropriété, plex de 2 à 5 logements), délais de vente ; province, RMR et secteurs & Mensuelle (brève) et trimestrielle (baromètres) & [I] \\
67+JLR (Equifax), Rapport mensuel sur le marché immobilier & Compilation des actes publiés au registre foncier : nombre de ventes et prix médians par type, RMR et région administrative ; seuil de 30 transactions ; exclusions documentées ; données détaillées par abonnement & Mensuelle & [I] \\
68+Indice Teranet--Banque Nationale & Indice de prix par ventes répétées, juin 2005 = 100, 11 RMR dont Montréal et Québec & Mensuelle & [I] \\
69+CBRE, \emph{Canadian Cap Rates \& Investment Insights} & Fourchettes de taux de capitalisation par classe d'actifs et par marché ; Montréal et Québec pour le multirésidentiel & Trimestrielle & [I] \\
70+Altus Group, \emph{Canadian Cost Guide} ; Marshall \& Swift ; RSMeans & Coûts de construction unitaires par type de bâtiment et par ville ; multiplicateurs locaux et temporels ; tableaux réservés aux abonnés & Annuelle & [I] \\
71+Bureau d'assurance du Canada (Infoassurance) & Prime moyenne d'assurance habitation au Québec & Annuelle & [I] \\
72+\end{longtable}
73+\end{footnotesize}
74+
75+\begin{footnotesize}
76+\begin{longtable}{@{}L{30mm}L{36mm}L{26mm}L{44mm}@{}}
77+\caption{Accès, licence, coût et adresse consultée pour les sources du \cref{tab:13:inventaire} (consultation du 31 octobre 2026)}\label{tab:13:acces}\\
78+\toprule
79+Source & Accès et coût & Licence & Adresse consultée \\
80+\midrule
81+\endfirsthead
82+\caption[]{Accès, licence, coût et adresse (suite)}\\
83+\toprule
84+Source & Accès et coût & Licence & Adresse consultée \\
85+\midrule
86+\endhead
87+\bottomrule
88+\endfoot
89+Registre foncier & Consultation en ligne avec compte client ; 1~\$ par document consulté (index des immeubles, acte, plan) ; état certifié 16~\$ plus 16~\$ par réquisition ; copie non certifiée 26~\$ ; grille en vigueur à compter du 1\textsuperscript{er} avril 2025 & Registre public ; documents consultables, usage dans le cadre du mandat & \url{https://www.quebec.ca/habitation-et-logement/information-fonciere/registre-foncier/utilisation-possible} \\
90+Rôles d'évaluation (MAMH) & Téléchargement libre (archive complète de plusieurs Go, géopackage, FGDB, fichiers XML par municipalité) ; gratuit & Creative Commons Attribution 4.0 ; interdiction de réidentifier les renseignements caviardés & \url{https://www.donneesquebec.ca/recherche/dataset/roles-d-evaluation-fonciere-du-quebec} \\
91+Unités d'évaluation et permis (Montréal) & CSV, GeoJSON, SHP ; gratuit & Licence ouverte de la Ville (CC BY 4.0) & \url{https://donnees.montreal.ca/dataset/unites-evaluation-fonciere} ; \url{https://donnees.montreal.ca/dataset/permis-construction} \\
92+Permis (Québec) & CSV, GeoJSON, SHP ; gratuit & CC BY 4.0 (Données Québec) & \url{https://www.donneesquebec.ca/recherche/dataset/permis-delivres-ville-de-quebec} \\
93+RFU et profil financier (MAMH) & CSV avec dictionnaire des postes ; gratuit & CC BY 4.0 & \url{https://www.donneesquebec.ca/recherche/dataset/richesse-fonciere-uniformisee} \\
94+MEFQ 2026 & PDF librement téléchargeables (cinq parties) ; gratuit & Droit d'auteur du gouvernement du Québec, sans licence ouverte : citer et renvoyer, ne pas reproduire les barèmes & \url{https://www.quebec.ca/habitation-territoire/information-fonciere/evaluation-fonciere/manuel/consulter} \\
95+SCHL, Enquête sur les logements locatifs & Tableaux Excel par RMR et portail de données ; gratuit & Conditions d'utilisation de la SCHL : reproduction non commerciale et éducative avec attribution ; ce n'est pas une licence ouverte & \url{https://www.cmhc-schl.gc.ca/fr/professionnels/marche-du-logement-donnees-et-recherche/marches-de-lhabitation/rapports-sur-le-marche-locatif} \\
96+Statistique Canada & CSV complets (archive zip) et service Web WDS ; gratuit & Licence ouverte de Statistique Canada : réutilisation et redistribution libres avec la mention « Source : Statistique Canada, tableau … » & \url{https://www150.statcan.gc.ca/t1/tbl1/fr/tv.action?pid=1810028901} (et tableaux cités) \\
97+Banque du Canada & API Valet (CSV, JSON) ; gratuit & Conditions d'utilisation : reproduction non commerciale et éducative avec la mention « Source : Banque du Canada » & \url{https://www.bankofcanada.ca/valet/} \\
98+TAL & Pages Web et formulaires PDF ; gratuit & Documents publics & \url{https://www.tal.gouv.qc.ca/fr/reconduction-du-bail-et-fixation-de-loyer/pourcentages-applicables-aux-criteres-de-fixation-de-loyer} \\
99+APCIQ / Centris & Communiqués et baromètres PDF gratuits ; données détaillées réservées aux courtiers et partenaires & Conditions d'utilisation de l'APCIQ : attribution obligatoire (« Source : APCIQ par le système Centris »), usage commercial interdit sans consentement écrit & \url{https://apciq.ca/en/terms-of-use/} \\
100+JLR & Rapport mensuel PDF gratuit ; données détaillées par abonnement (modalités non vérifiées) & Droit d'auteur ; citation « Source : JLR.ca » & \url{https://solutions.jlr.ca/hubfs/Etudes_et_rapports/2026-09_Rapport_Mensuel.pdf} \\
101+Teranet--Banque Nationale & Communiqué mensuel PDF gratuit (adresse réutilisée chaque mois) ; séries téléchargeables (conditions non lues : certificat de sécurité invalide le jour de la consultation) & Droit d'auteur & \url{https://www.nbc.ca/content/dam/bnc/taux-analyses/analyse-eco/logement/economic-news-teranet.pdf} \\
102+CBRE & Rapport PDF sur inscription ; gratuit & Droit d'auteur ; valeurs ponctuelles citées avec attribution & \url{https://www.cbre.ca/insights/reports/canada-cap-rates-investment-insights-q3-2026} \\
103+Altus, Marshall \& Swift, RSMeans & Abonnement ou achat ; tableaux protégés & Droit d'auteur ; aucune reproduction sans licence & \url{https://www.altusgroup.com/featured-insights/canadian-cost-guide/} \\
104+\end{longtable}
105+\end{footnotesize}
106+
107+\subsection{Les registres : registre foncier, rôle d'évaluation, permis}
108+
109+Le \motcle{registre foncier} est la source première de toute donnée de transaction au Québec. La publicité des droits s'y opère par l'inscription des actes sur le registre tenu par l'Officier de la publicité foncière, et un droit inscrit est présumé connu de celui qui acquiert \parencite[art.~2934, 2941 et 2943]{ccq}. Le registre comprend, pour chaque circonscription foncière, un index des immeubles composé d'autant de fiches qu'il y a d'immeubles immatriculés \parencite[art.~2969--2972.1]{ccq} ; le contenu de la fiche immobilière est fixé par le Règlement sur la publicité foncière \parencite[art.~4--5]{reglPubliciteFonciere}. Pour l'évaluateur, la fiche d'un lot énumère la chaîne des actes qui l'ont visé (ventes, hypothèques, radiations, servitudes, avis de contamination, préavis d'exercice d'un droit hypothécaire), avec la nature de l'acte, sa date d'inscription, les parties et, pour les ventes, la contrepartie déclarée. L'acte lui-même, consultable en ligne, donne la désignation cadastrale, le prix, les modalités de paiement, les déclarations des parties exigées aux fins des droits sur les mutations immobilières, la garantie consentie et les meubles inclus. Le registre est public ; sa consultation exige un compte client et se paie à l'unité, selon la grille tarifaire décrite dans l'encadré ci-dessous. Il faut savoir ce que le registre ne contient pas : ni la date de la promesse d'achat, ni les conditions de la mise en marché, ni l'état physique de l'immeuble, ni, en général, la répartition du prix entre terrain et bâtiment.
110+
111+Le \motcle{rôle d'évaluation foncière} est la seconde source de masse. Chaque unité d'évaluation y figure avec sa valeur réelle, établie selon les conditions du marché au 1\textsuperscript{er} juillet du deuxième exercice précédant l'entrée en vigueur du rôle \parencite[art.~42 et 46]{lfm}, et avec les renseignements prescrits par le Règlement sur le rôle d'évaluation foncière, qui renvoie lui-même au Manuel d'évaluation foncière du Québec \parencite[art.~1--2]{reglrole}. Depuis que ce règlement autorise le ministre à diffuser le rôle « en format ouvert » sans les noms et adresses des propriétaires \parencite[art.~20]{reglrole}, le ministère des Affaires municipales et de l'Habitation publie sur Données Québec l'ensemble des rôles municipaux sous licence Creative Commons Attribution 4.0 \parencite{dqroles}. Le jeu annonce 1~140 fichiers XML, un par municipalité ; l'index de 2026 déposé dans le dépôt de l'ouvrage en compte 1~134, l'écart tenant vraisemblablement aux territoires non organisés et aux regroupements, et la structure des fichiers est décrite dans le Répertoire des renseignements prescrits, version 2.5 \parencite{dqrepertoire}, dont l'exemple intégral de la \cref{sec:13:exemple} donne la lecture. Les champs disponibles pour chaque unité comprennent l'adresse, le code d'utilisation prédominante, le numéro d'unité de voisinage, la superficie du terrain, l'année de construction et son caractère réel ou estimé, l'aire d'étages du bâtiment principal, le nombre de logements, la date à laquelle les conditions du marché ont été considérées, les valeurs du terrain, du bâtiment et de l'immeuble au rôle en vigueur et la valeur au rôle antérieur. Les renseignements concernant les cadastres, les noms des propriétaires et leurs adresses postales sont caviardés, et la licence interdit expressément de réidentifier les renseignements personnels ainsi retirés. Pour l'évaluateur, le rôle est à la fois une source de caractéristiques physiques homogènes sur tout le territoire et une source de valeurs dont il faut toujours rappeler qu'elles sont fiscales, datées et établies pour l'équité d'ensemble plutôt que pour la justesse individuelle (\cref{ch:09}).
112+
113+Les municipalités complètent ces registres par leurs propres jeux ouverts. La Ville de Montréal publie chaque semaine ses unités d'évaluation foncière, avec le code d'utilisation, le nombre de logements, l'année de construction et les superficies de terrain et de bâtiment, dans un découpage sans valeur légale \parencite{mtlunites}, ainsi que les permis de construction, de transformation et de démolition, dont les coûts déclarés sont expressément donnés sans valeur légale \parencite{mtlpermis} ; la Ville de Québec publie les permis délivrés et son zonage \parencite{vqpermis}. Ni Québec ni Gatineau ne publient de jeu « rôle » propre : leurs rôles passent par le jeu du ministère. Les permis sont une source précieuse pour dater les travaux qui expliquent l'état d'un comparable, mais le coût qui y est déclaré est celui qu'a indiqué le requérant au moment de la demande, non un coût réel constaté ; il ne peut servir qu'à titre indicatif et jamais comme donnée de coût de la méthode du coût (\cref{ch:21,ch:23}).
114+
115+\begin{quebec}[{{Consulter le registre foncier : contenu, limites et tarifs}}]
116+Le registre foncier du Québec est un registre public, et la page gouvernementale qui en décrit l'utilisation rappelle que « la lecture de l'Index des immeubles n'est pas toujours suffisante » pour connaître l'état des droits sur un immeuble \parencite{registreFoncierUtilisation} : l'index renvoie aux actes, et c'est l'acte qui porte le prix, les conditions et la garantie. Les montants de base des droits de publicité foncière sont fixés à l'annexe I de la Loi sur les bureaux de la publicité des droits \parencite[art.~7.2 et annexe~I]{loiBureauxPublicite} et indexés ; la grille tarifaire en vigueur à compter du 1\textsuperscript{er} avril 2025, publiée sous l'intitulé « janvier 2026 », prévoit une consultation à 1~\$ par document (index des immeubles, acte ou plan cadastral), une inscription à 142~\$, une radiation à 162~\$, un état certifié à 16~\$ plus 16~\$ par réquisition, une copie non certifiée à 26~\$ et une copie certifiée à 42~\$ \parencite{registreFoncierTarifs2026}. Aucune grille postérieure au 1\textsuperscript{er} avril 2025 n'a été trouvée au moment de la rédaction ; l'évaluateur vérifie le tarif courant avant de budgéter une collecte. Pour vingt-cinq comparables candidats, la lecture de l'index et de l'acte de chacun coûte ainsi une cinquantaine de dollars, ce qui est dérisoire au regard du coût d'une erreur de comparable ; la contrainte réelle est le temps de lecture et d'extraction, que l'exemple de la \cref{sec:13:exemple} et le \cref{ch:14} cherchent à réduire par l'automatisation de tout ce qui peut l'être.
117+\end{quebec}
118+
119+\subsection{Les données du courtage et leurs dérivés}
120+
121+Le système Centris, exploité par l'APCIQ, enregistre les inscriptions et les ventes conclues par l'intermédiaire d'un courtier immobilier. Ses statistiques trimestrielles donnent, pour la province, chaque région métropolitaine et chaque secteur, le nombre de ventes, le nombre d'inscriptions en vigueur, les prix médians par catégorie de propriété et les délais de vente : au troisième trimestre de 2026, \num{20935} ventes résidentielles dans la province, en baisse de 7~\% sur un an, \num{42408} inscriptions en vigueur, en hausse de 20~\%, et des prix médians de \dollars{500000} pour l'unifamiliale, \dollars{400000} pour la copropriété et \dollars{699000} pour les plex de deux à cinq logements \parencite{apciqt32026}. La richesse de ces données tient à ce qu'elles proviennent de la fiche de courtage : date d'inscription, prix demandé et ses révisions, date d'acceptation de la promesse d'achat, inclusions et exclusions, description physique, photographies. C'est la seule source qui connaisse la date à laquelle le prix a été convenu, et la seule qui documente la mise en marché. Sa limite est double. Elle ne couvre que les ventes conclues par courtier, de sorte que ses médianes décrivent le marché du courtage et non l'ensemble des transferts ; et l'accès aux données détaillées est réservé aux courtiers et à leurs partenaires, le public n'ayant accès qu'aux agrégats, dont la reproduction est soumise aux conditions d'utilisation de l'association \parencite{apciqconditions}.
122+
123+JLR, filiale d'Equifax, compile pour sa part les actes publiés au registre foncier et en tire un rapport mensuel gratuit et des données détaillées par abonnement. Sa méthodologie, telle que documentée dans le rapport, retient les transactions du registre foncier, exige un seuil de trente transactions pour publier une médiane et exclut les ventes de moins de \dollars{5000}, les ventes liées, les reprises de finance, les ventes de parts indivises et les ventes multiples \parencite{jlr202609}. En septembre 2026, les médianes provinciales sur douze mois s'établissaient à \dollars{483000} pour l'unifamiliale et à \dollars{622000} pour les immeubles de deux à cinq logements, en hausse de 7 et 9~\% respectivement ; en août 2026, pour la partie québécoise de la RMR d'Ottawa-Gatineau, la médiane mensuelle de l'unifamiliale était de \dollars{522741}, la médiane sur douze mois de \dollars{495000} et le ratio du prix de vente à l'évaluation municipale de 111~\% \parencite{jlr202608}. Ces chiffres diffèrent de ceux de l'APCIQ pour les mêmes territoires et les mêmes périodes, et la différence n'est pas une erreur : les univers ne sont pas les mêmes, l'un couvrant toutes les ventes publiées après exclusions, l'autre les seules ventes par courtier, et les dates ne sont pas les mêmes, l'un datant à l'acte publié, l'autre à l'acceptation de la promesse. L'évaluateur qui cite l'une ou l'autre doit dire laquelle et pourquoi.
124+
125+L'indice Teranet--Banque Nationale, enfin, est un indice de prix par ventes répétées calculé mensuellement pour onze régions métropolitaines canadiennes, base juin 2005 = 100, à partir des mutations enregistrées ; en septembre 2026, l'indice composite s'établissait à 294,94, en recul de 3,5~\% sur un an, celui de Montréal à 310,62, en hausse de 1,4~\%, et celui de Québec à 327,56, en hausse de 7,7~\% \parencite{teranet202609}. La méthode des ventes répétées, due à \textcite{baileyMuthNourse1963} et popularisée par \textcite{caseShiller1987}, est exposée au \cref{ch:35} ; on retiendra ici qu'elle n'utilise que les immeubles vendus au moins deux fois, ce qui en fait un indice de qualité constante mais sur un sous-échantillon particulier du parc, et que les indices de ce type sont révisés à mesure que de nouvelles paires de ventes s'ajoutent, comme \textcite{silverstein2014} l'a documenté pour les indices américains.
126+
127+\subsection{Les statistiques officielles : Statistique Canada, SCHL, Banque du Canada, TAL}
128+
129+Statistique Canada ne publie aucun tableau de prix de vente des propriétés résidentielles au Québec : les prix de transaction y sont d'industrie, et c'est la première chose à savoir pour ne pas chercher ce qui n'existe pas. L'organisme publie en revanche les trois indices dont l'évaluateur a besoin pour transporter des montants dans le temps et qu'il ne doit jamais confondre : l'Indice des prix des logements neufs (IPLN, tableau 18-10-0205-01), qui mesure le prix de vente des maisons neuves des constructeurs, terrain compris ou non \parencite{statcanipln} ; l'Indice des prix de la construction de bâtiments (IPCB, tableau 18-10-0289-01), qui mesure le prix facturé par les entrepreneurs pour construire des bâtiments types \parencite{statcanIPCB} ; et l'Indice des prix à la consommation (IPC, tableau 18-10-0004-01), dont la composante « loyer » intéresse directement l'analyse locative \parencite{statcanipc}. La \cref{sec:13:couts} en détaille la structure. L'organisme diffuse aussi, pour le compte de la SCHL, les mises en chantier \parencite{statcanmiseschantier}, les estimations de population des régions métropolitaines \parencite{statcanpoprmr} et les tableaux du recensement de 2021 sur le type de construction et le mode d'occupation \parencite{statcanrec2021tenure} : au Québec, \num{3749035} ménages privés, dont 59,9~\% de propriétaires, contre 66,5~\% au Canada. Tous ces tableaux sont diffusés sous la Licence ouverte de Statistique Canada, qui autorise la réutilisation et la redistribution avec mention de la source, et sont accessibles en téléchargement complet ou par le service Web WDS, ce qui permet de les intégrer à une chaîne de données reproductible.
130+
131+La SCHL est la seule source d'ampleur sur les loyers payés et l'inoccupation. Son Enquête sur les logements locatifs, menée chaque mois d'octobre et publiée en décembre, couvre les appartements d'initiative privée dans les immeubles de trois logements et plus, par région métropolitaine, par zone, par taille de logement, par taille d'immeuble et par année de construction, et publie des tableaux distincts pour les copropriétés offertes en location \parencite{schlLocatif2025,schlrmltables2025}. La \cref{sec:13:loyers} en explique la méthode et les indicateurs de qualité. Ses tableaux sont publics mais diffusés sous les conditions d'utilisation de la société, qui autorisent la reproduction non commerciale et éducative avec attribution ; ce n'est pas une licence ouverte, et l'ouvrage, comme tout rapport, cite les chiffres sans redistribuer les fichiers.
132+
133+La Banque du Canada publie par son API Valet les séries dont le \cref{ch:31} a besoin : le taux cible du financement à un jour, porté à 2,50~\% le 28 octobre 2026, les rendements des obligations de référence à cinq et dix ans, le taux hypothécaire conventionnel affiché à cinq ans et les taux effectifs des fonds avancés déclarés par les banques \parencite{bdcvalet,bdc20261028}. Le Tribunal administratif du logement, enfin, ne diffuse pas de base de loyers : il publie chaque année les pourcentages applicables aux critères de fixation de loyer, le formulaire de renseignements nécessaires à la fixation et des résumés de décisions \parencite{talIndices2026,talFormulaireRN2026,talResumesDecisions}. Ces documents sont des données de droit plus que des données de marché, mais la \cref{sec:13:loyers} montre qu'ils structurent la lecture de tout état locatif.
134+
135+\section{Accès, licences, coûts et confidentialité}\label{sec:13:licences}
136+
137+\subsection{Quatre régimes de réutilisation}
138+
139+Les sources du \cref{tab:13:acces} relèvent de quatre régimes juridiques que l'évaluateur doit distinguer avant d'intégrer une donnée à un rapport, à une base ou à une publication. Le premier est la \motcle{licence ouverte}, dont la Licence ouverte de Statistique Canada et la licence Creative Commons Attribution 4.0 de Données Québec sont les exemples : la donnée peut être copiée, transformée, redistribuée et intégrée à un produit, y compris commercial, à la seule condition d'attribuer la source. C'est pourquoi les fichiers bruts de Statistique Canada et du ministère des Affaires municipales et de l'Habitation peuvent être déposés tels quels dans le dépôt de l'ouvrage, avec leur adresse et leur date de téléchargement, et pourquoi les tableaux du présent chapitre qui en découlent portent la mention « Source : Statistique Canada, tableau … » ou « Source : MAMH ». La licence CC BY des rôles ajoute une condition propre : l'interdiction de réidentifier les renseignements personnels caviardés, qui lie quiconque croise le rôle ouvert avec une autre source.
140+
141+Le deuxième régime est celui des \motcle{conditions d'utilisation} d'un organisme public qui autorise la reproduction à des fins non commerciales et éducatives avec attribution, sans concéder de licence ouverte : c'est le cas de la Banque du Canada et de la SCHL. Les chiffres peuvent être cités dans un rapport ou un ouvrage avec la mention de la source ; les fichiers ne doivent pas être redistribués dans un produit publié, et un usage commercial de la donnée elle-même, par exemple sa revente dans un service, exige une autorisation. Le troisième régime est celui des \motcle{documents publics protégés par le droit d'auteur} de la Couronne ou d'une municipalité : le Manuel d'évaluation foncière, les règlements municipaux de taxation, les pages du Tribunal administratif du logement. Ils sont librement consultables ; leurs faits (un taux, une date, une règle) peuvent être rapportés et leurs passages brièvement cités ; leurs tableaux et barèmes ne peuvent pas être reproduits sans autorisation, ce qui vaut en particulier pour les barèmes de coûts de la partie 3E du Manuel \parencite{mefq2026}. Le quatrième régime est celui des \motcle{données d'industrie} protégées par le droit d'auteur et des conditions contractuelles : l'APCIQ exige l'attribution et interdit l'usage commercial sans consentement écrit \parencite{apciqconditions}, JLR prescrit la mention « Source : JLR.ca », les guides de coûts n'autorisent aucune reproduction de leurs tableaux. Dans un rapport d'évaluation, la règle pratique est la même pour tous : citer des valeurs ponctuelles avec la source, l'édition et la date, ne jamais reproduire un tableau entier, et conserver au dossier la pièce consultée.
142+
143+\subsection{Le coût des données}
144+
145+Le coût d'acquisition des données immobilières au Québec est faible pour les sources ouvertes et les agrégats, et il se concentre sur trois postes. Le premier est la consultation du registre foncier, facturée à l'unité ; pour un dossier résidentiel ordinaire qui exige la vérification d'une dizaine de comparables et du sujet, il se compte en dizaines de dollars. Le deuxième est l'accès aux données détaillées d'industrie, par abonnement professionnel au système de courtage ou à un compilateur du registre, dont le prix n'est pas public et n'a pas été vérifié pour cet ouvrage ; il représente le poste principal pour un cabinet qui traite des volumes. Le troisième est le temps d'extraction, de normalisation et de vérification, qui dépasse de loin les deux premiers dès que le travail n'est pas automatisé. Une base d'évaluation bien conçue (\cref{sec:13:base}) transforme ce coût variable, payé à chaque dossier, en un coût fixe payé une fois, et c'est son principal argument économique.
146+
147+\subsection{Confidentialité, secret professionnel et renseignements personnels}
148+
149+Trois corps de règles encadrent l'usage des données personnelles que l'évaluateur manipule. Le registre foncier est public par nature : les noms des parties à une vente y figurent parce que la publicité des droits l'exige, et leur consultation dans le cadre d'un mandat est légitime. Il n'en découle pas que ces noms puissent être compilés, diffusés ou croisés librement : la législation québécoise sur la protection des renseignements personnels s'applique à toute collecte et à toute communication qui excèdent la finalité pour laquelle un renseignement est accessible, et l'évaluateur consulte un juriste avant de constituer un fichier nominatif ou de publier un travail qui permettrait d'identifier des personnes. Le rôle d'évaluation en format ouvert a précisément été caviardé pour cette raison, et la licence en interdit la réidentification. Le deuxième corps de règles est déontologique : l'évaluateur agréé est tenu au secret professionnel \parencite[art.~51]{deontOEAQ}, son rapport doit faire état de la méthodologie et de l'étendue des recherches \parencite[art.~41]{deontOEAQ}, et le Règlement sur la tenue des dossiers lui impose de conserver chaque dossier pendant au moins cinq ans après le dernier service rendu et d'en assurer la confidentialité \parencite[art.~6--7]{tenuedossiersoeaq}. Les normes de pratique exigent que le rapport décrive l'étendue du travail et le traitement de l'information \parencite[règle 2.3]{oeaqNormes} : la provenance de chaque donnée, sa date et ses limites font partie du rapport. Le troisième corps de règles est contractuel : les conditions d'accès aux données d'industrie limitent souvent la rediffusion des données individuelles, et la fiche descriptive d'un courtier, qui contient les déclarations du propriétaire, n'est pas un document public même si elle a été largement diffusée pendant la mise en marché. La règle de conduite qui résume ces trois corps est simple : collecter ce dont le mandat a besoin, conserver ce que la norme exige, publier des agrégats et des résultats, jamais des personnes.
150+
151+\section{Les données de transaction}\label{sec:13:transactions}
152+
153+\subsection{Trois enregistrements pour une même vente}
154+
155+Une vente résidentielle laisse au moins trois traces documentaires, et l'évaluateur doit savoir ce que chacune contient. L'\motcle{acte de vente} publié au registre foncier est la trace juridique : il identifie les parties, désigne l'immeuble par ses lots, énonce le prix et ses modalités, stipule la garantie (légale, ou exclue « aux risques et périls de l'acheteur » lorsque les art.~1732 et 1733 du Code civil le permettent), énumère les meubles inclus et contient les déclarations exigées par la Loi concernant les droits sur les mutations immobilières. La \motcle{fiche de courtage} est la trace commerciale : elle documente la mise en marché (date d'inscription, prix demandé et révisions, délai, date d'acceptation de la promesse d'achat, inclusions et exclusions, description, photographies) et elle est accompagnée des déclarations du vendeur sur l'immeuble, formulaire obligatoire depuis juillet 2012 pour la vente par une personne physique d'un immeuble résidentiel de moins de cinq logements \parencite{oaciqFormulaireDV,oaciqContexteDV}, dont la section D2.6 recueille notamment les loyers annuels et les dossiers devant le Tribunal administratif du logement. Le Règlement sur les conditions d'exercice d'une opération de courtage oblige le courtier à vérifier les renseignements qu'il fournit et à indiquer sur la fiche descriptive la mention « vendu sans garantie légale » le cas échéant \parencite[art.~5 et 118]{reglCourtageDeontologie}. L'\motcle{inscription au rôle}, enfin, est la trace fiscale : elle décrit l'unité (terrain, bâtiment, logements, utilisation) selon les conventions du Manuel et porte sa valeur à la date de référence. Le \cref{tab:13:champs} croise les champs utiles à l'évaluation et les sources qui les fournissent ; il montre qu'aucune source ne suffit seule et que la base d'évaluation de la \cref{sec:13:base} est d'abord un appariement.
156+
157+\begin{table}[tbp]
158+\caption{Champs d'une transaction résidentielle et sources qui les fournissent au Québec (\checkmark~: champ présent ; (\checkmark)~: présent mais déclaratif ou partiel)}\label{tab:13:champs}
159+\centering\small
160+\begin{tabularx}{\textwidth}{@{}Yccc@{}}
161+\toprule
162+Champ & Acte publié (registre foncier) & Fiche de courtage et déclarations du vendeur & Rôle (format ouvert) \\
163+\midrule
164+Désignation cadastrale, adresse & \checkmark & \checkmark & adresse seulement (lot caviardé) \\
165+Parties à la vente, lien entre elles & \checkmark & (\checkmark) & --- \\
166+Prix convenu & \checkmark & \checkmark & --- \\
167+Date de la promesse d'achat (prix convenu) & --- & \checkmark & --- \\
168+Date de signature et d'inscription de l'acte & \checkmark & (\checkmark) & --- \\
169+Meubles inclus, inclusions et exclusions & (\checkmark) & \checkmark & --- \\
170+Garantie légale ou exclusion & \checkmark & \checkmark & --- \\
171+Financement (balance de vente, hypothèque) & \checkmark & (\checkmark) & --- \\
172+Conditions de vente (succession, reprise, vente liée) & (\checkmark) déductibles des parties et des déclarations & (\checkmark) & --- \\
173+Mise en marché (prix demandé, délai) & --- & \checkmark & --- \\
174+Caractéristiques physiques (superficie, âge, logements) & --- & (\checkmark) déclaratives & \checkmark selon les conventions du MEFQ \\
175+État, travaux, sinistres, vices connus & --- & \checkmark (déclarations du vendeur) & --- \\
176+Loyers et baux & --- & (\checkmark) section D2.6 & nombre de logements seulement \\
177+Valeur au rôle, date de référence, utilisation (CUBF) & --- & (\checkmark) reprise par le courtier & \checkmark \\
178+\bottomrule
179+\end{tabularx}
180+\tabsource{synthèse de l'auteur d'après \textcite{ccq}, \textcite{reglPubliciteFonciere}, \textcite{oaciqFormulaireDV}, \textcite{reglCourtageDeontologie} et \textcite{dqrepertoire} (notes de vérification du dépôt, lots A et B).}
181+\end{table}
182+
183+\subsection{Le prix observé comme mesure du prix convenu}
184+
185+Le \cref{ch:16} a défini la valeur marchande comme l'espérance conditionnelle du prix et traité le prix observé $P_j$ comme une réalisation bruitée de cette espérance. Il faut maintenant reconnaître que le prix \emph{observé} dans une source n'est pas toujours le prix \emph{convenu} pour l'immeuble. Notons $P_j^{\ast}$ le prix effectivement convenu pour le seul immeuble, à la date de la promesse, et $\tilde P_j$ le montant lu dans l'acte. Le second s'écrit
186+\begin{equation}\label{eq:13:acte}
187+\tilde P_j = P_j^{\ast} + M_j + F_j + \eta_j ,
188+\end{equation}
189+où $M_j$ est la contrepartie attribuée aux meubles inclus, $F_j$ la valeur de toute condition de financement ou de toute contrepartie non monétaire qui n'apparaît pas comme prix et $\eta_j$ une erreur de transcription ou de déclaration. L'\cref{eq:13:acte} dit trois choses. D'abord, la contrepartie pour les meubles doit être retranchée avant toute comparaison, ce qui suppose de la connaître ; l'acte la mentionne parfois, la fiche de courtage l'énumère toujours en nature, rarement en valeur, et la valeur attribuée aux meubles dans un acte est souvent fixée pour des raisons fiscales plutôt qu'économiques. Ensuite, un prix d'acte n'est un prix comptant que si $F_j = 0$ : une balance de prix de vente à taux réduit ou une reprise d'hypothèque avantageuse transfèrent une partie de la contrepartie dans le financement, et la définition professionnelle de la valeur marchande exige un prix en argent comptant ou l'équivalent \parencite{oeaqNormes}. Enfin, l'erreur $\eta_j$ n'est pas théorique : les prix à \dollars{1}, les prix de parts indivises inscrits comme prix d'immeuble et les contreparties « et autres bonnes et valables considérations » existent dans tout extrait du registre, et le \cref{ch:14} montre comment les détecter.
190+
191+La Loi concernant les droits sur les mutations immobilières fournit un garde-fou indirect. La base d'imposition du droit de mutation est le plus élevé de trois montants : la contrepartie fournie, la contrepartie stipulée et la valeur marchande de l'immeuble au moment du transfert, cette dernière étant, aux fins de la loi, la valeur inscrite au rôle multipliée par le facteur comparatif \parencite[art.~1.1 et 2]{ldmi} :
192+\begin{equation}\label{eq:13:ldmi}
193+B_j = \max\left(C_j^{\text{fournie}},\ C_j^{\text{stipulée}},\ V_j^{\text{rôle}}\,F\right),
194+\end{equation}
195+où $F$ est le facteur comparatif du rôle pour l'exercice. Il s'ensuit qu'un prix déclaré très inférieur à la valeur uniformisée n'échappe pas au droit et que la déclaration des parties aux fins de cette loi, qui figure dans l'acte, informe sur la nature du transfert : les exonérations de l'art.~20, qui visent notamment les transferts en ligne directe ascendante ou descendante et entre conjoints \parencite[art.~20]{ldmi}, sont autant de signaux d'une vente liée, et les municipalités avisées par l'Officier de la publicité foncière dans les quinze jours de l'inscription \parencite[art.~10]{ldmi} disposent ainsi d'un fichier des mutations que le Manuel d'évaluation foncière leur impose de tenir et de conserver pendant au moins quatre ans \parencite[art.~3]{reglrole}. Le ratio du prix de vente à la valeur au rôle,
196+\begin{equation}\label{eq:13:ratio}
197+r_j = \frac{\tilde P_j - M_j}{V_j^{\text{rôle}}\,F},
198+\end{equation}
199+est l'indicateur le plus simple dont dispose l'évaluateur pour repérer, dans un extrait de registre, les transactions dont le prix s'écarte anormalement de la valeur fiscale uniformisée : c'est la grandeur que JLR publie comme ratio prix/évaluation (111~\% dans la partie québécoise de la RMR d'Ottawa-Gatineau en août 2026) et c'est, sous la forme inverse $V/P$, la statistique que le Règlement sur la proportion médiane épure en retirant les ratios situés à plus de deux écarts-types de la médiane \parencite[art.~15--16]{reglpropmed}. Un ratio de 0,5 ou de 2 n'est pas nécessairement une erreur, mais il exige une vérification.
200+
201+\subsection{La date : promesse d'achat, acte, inscription}
202+
203+Une vente informe sur le marché à la date où le prix a été convenu, c'est-à-dire à l'acceptation de la promesse d'achat. L'acte notarié est signé plus tard, à une date fixée par les parties en fonction du financement, de l'inspection et du déménagement, et il est inscrit au registre peu après sa signature. Le délai entre la promesse et l'acte est couramment de un à trois mois pour une revente ordinaire, davantage pour une vente conditionnelle à la vente d'une autre propriété et beaucoup plus pour une vente sur plans. Seule la fiche de courtage connaît la date de la promesse ; le registre foncier, JLR et le rôle ne connaissent que la date de l'acte. Lorsque la vente sert de comparable, son prix est transporté à la date d'évaluation $t_e$ par un indice de conditions du marché $I$ :
204+\begin{equation}\label{eq:13:temps}
205+P_j(t_e) = P_j^{\ast}\,\frac{I(t_e)}{I(t_p)} ,
206+\end{equation}
207+où $t_p$ est la date de la promesse. Si l'on date à tort la vente à l'acte, $t_a = t_p + \delta$, l'ajustement appliqué est $I(t_e)/I(t_a)$ au lieu de $I(t_e)/I(t_p)$, et le prix transporté est multiplié par $I(t_p)/I(t_a)$. Dans un marché dont les prix croissent au taux annuel composé $g$, ce facteur vaut $(1+g)^{-\delta/12}$, et le biais relatif sur le prix ajusté est
208+\begin{equation}\label{eq:13:biais}
209+b(g,\delta) = (1+g)^{-\delta/12} - 1 \approx -\frac{g\,\delta}{12} .
210+\end{equation}
211+
212+\begin{proposition}{Signe et ampleur du biais de datation}{datation}
213+Si les prix du marché évoluent de façon monotone entre la promesse et l'acte, dater un comparable à l'acte plutôt qu'à la promesse biaise systématiquement le prix ajusté dans le sens contraire de l'évolution du marché : sous-estimation en marché haussier, surestimation en marché baissier. Pour un délai $\delta$ en mois et une croissance annuelle $g$, l'ampleur du biais est $(1+g)^{-\delta/12} - 1$, soit environ $-g\delta/12$ ; pour $g = 6~\%$ et $\delta = 3$ mois, elle vaut $-1{,}5~\%$ du prix, et le biais est le même pour tous les comparables datés de la même façon, de sorte qu'il ne se compense pas en moyenne.
214+\end{proposition}
215+\begin{proof}
216+Sous une croissance composée, $I(t) = I(t_p)(1+g)^{(t-t_p)/12}$ pour $t \geq t_p$. Le prix transporté avec la mauvaise date vaut $P_j^{\ast} I(t_e)/I(t_a) = P_j^{\ast}\,\frac{I(t_e)}{I(t_p)}\,\frac{I(t_p)}{I(t_a)} = P_j(t_e)\,(1+g)^{-\delta/12}$. Le biais relatif est donc $(1+g)^{-\delta/12} - 1$, négatif si $g > 0$ et positif si $g < 0$ ; le développement au premier ordre donne $-g\delta/12$. Comme $\delta$ et $g$ sont communs à tous les comparables d'un même marché datés à l'acte, le biais est systématique et ne s'annule pas par moyenne.
217+\end{proof}
218+
219+Le remède est de reconstituer la date de la promesse, à partir de l'historique de courtage lorsqu'il est accessible, et sinon de reculer la date de l'acte d'un délai typique documenté au dossier ; la \cref{sec:13:sensibilite} chiffre le biais pour des valeurs réalistes de $g$ et de $\delta$. Deux précisions complètent la règle. La première est que les indices de prix eux-mêmes diffèrent par leur convention de date : les statistiques de l'APCIQ datent à l'acceptation de la promesse, celles de JLR et l'indice Teranet--Banque Nationale à l'acte publié ; un indice daté à l'acte appliqué à des comparables datés à la promesse introduit le même décalage en sens inverse. La seconde est que le délai n'est pas constant : il s'allonge quand le financement se resserre et pour les ventes conditionnelles, et il est beaucoup plus long pour les ventes sur plans, où le prix a pu être convenu un ou deux ans avant la livraison, ce qui disqualifie ces ventes comme indicateurs du marché à la date de l'acte.
220+
221+\subsection{Les ventes non représentatives et leurs signatures}
222+
223+La définition de la valeur marchande suppose une vente sincère entre parties informées et non contraintes, après une exposition suffisante et pour un prix comptant \parencite{oeaqNormes}. Le \cref{ch:16} a énuméré les ventes qui ne satisfont pas ces conditions ; le présent chapitre s'intéresse à la manière dont chacune se signale dans les données, parce que l'évaluateur qui travaille sur un extrait de registre ou une base d'industrie ne peut pas interroger les parties de chaque vente. Le \cref{tab:13:pieges} recense les cas les plus fréquents, l'indice qui permet de les détecter dans les champs disponibles et le traitement recommandé. Trois d'entre eux appellent un commentaire.
224+
225+Les \motcle{ventes liées}, entre personnes physiques ou morales apparentées, se signalent par l'identité des noms de famille, par la coïncidence d'adresses, par le transfert entre une personne et une société qu'elle contrôle et, dans l'acte, par la déclaration d'une exonération du droit de mutation fondée sur le lien de parenté ou le contrôle \parencite[art.~19--20]{ldmi}. JLR les exclut de ses médianes ; la plupart des bases municipales les codent dans le fichier des mutations. Elles ne sont pas toujours en deçà du marché, mais leur prix n'a pas été fixé par le marché, et elles sont exclues.
226+
227+Les \motcle{ventes successorales et sous contrainte} sont plus délicates. Une vente par une succession, un liquidateur ou des ex-conjoints qui se séparent peut être parfaitement représentative si elle a été menée par un courtier avec une exposition normale, et le registre ne permet pas de le savoir : la fiche de courtage, elle, le dit, par le délai de mise en marché et le prix demandé. Les \motcle{reprises de finance} se reconnaissent dans la chaîne des titres : préavis d'exercice d'un droit hypothécaire, puis prise en paiement, qui exige l'autorisation du tribunal lorsque le débiteur a acquitté la moitié ou plus de l'obligation \parencite[art.~2778]{ccq}, puis vente par l'institution prêteuse ; la vente sous contrôle de justice, quant à elle, est faite sans garantie de qualité \parencite[art.~1731]{ccq}. Le vendeur institutionnel et l'absence de garantie sont des signatures fiables. Ces ventes sont exclues des comparables, mais elles ne sont pas sans intérêt pour l'étude de marché, où leur fréquence est un indicateur de tension (\cref{ch:15}).
228+
229+Les \motcle{ventes de promoteur} posent un problème propre au neuf : le prix inclut souvent les taxes de vente, des incitatifs (électroménagers, stationnement, frais de notaire) et parfois une contrepartie pour des améliorations choisies après la promesse ; la date de la promesse peut précéder l'acte de plus d'un an ; et le contrat préliminaire, avec sa faculté de dédit de dix jours \parencite[art.~1785]{ccq}, n'est pas publié. On ne les compare qu'entre elles, et jamais sans connaître le traitement des taxes, ce qui renvoie au \cref{ch:21}.
230+
231+\begin{table}[tbp]
232+\caption{Pièges des données de transaction : signature dans les sources et traitement}\label{tab:13:pieges}
233+\centering\footnotesize
234+\begin{tabularx}{\textwidth}{@{}L{27mm}YL{33mm}@{}}
235+\toprule
236+Situation & Signature dans les données & Traitement \\
237+\midrule
238+Vente liée (parenté, conjoints, société contrôlée) & Noms ou adresses communs ; transfert personne physique $\leftrightarrow$ personne morale ; déclaration d'exonération du droit de mutation (art.~19--20 LDMI) ; ratio prix/rôle très faible & Exclure ; coder la raison au journal des exclusions \\
239+Succession, séparation, liquidation & Vendeur : succession, liquidateur ; plusieurs vendeurs ; fiche de courtage ou non & Vérifier l'exposition au marché ; conserver si mise en marché normale, sinon exclure \\
240+Reprise de finance, vente sous contrôle de justice & Préavis d'exercice puis prise en paiement à l'index ; vendeur institutionnel ; vente sans garantie de qualité (art.~1731 C.c.Q.) & Exclure des comparables ; compter dans l'étude de marché \\
241+Vente à un locataire ou à un voisin sans mise en marché & Aucune inscription de courtage ; acheteur déjà à l'adresse & Vérifier ; exclure si le prix n'a pas été exposé au marché \\
242+Vente de promoteur (neuf) & Vendeur : entreprise de construction ; premier transfert d'un lot nouvellement créé ; taxes et incitatifs inclus ; promesse très antérieure à l'acte & Comparer seulement entre ventes neuves ; isoler les taxes et incitatifs \\
243+Vente multiple, part indivise, démembrement & Plusieurs lots dans un même acte ; fraction indiquée ; usufruit, emphytéose & Exclure ou répartir le prix si la répartition est stipulée ; droits transmis différents (\cref{ch:02}) \\
244+Prix symbolique ou nominal & Prix $<$ \dollars{5000} (seuil du Règlement sur la proportion médiane) ; « autres bonnes et valables considérations » & Exclure \\
245+Meubles inclus & Clause de l'acte ; liste d'inclusions de la fiche ; valeur attribuée souvent fiscale & Retrancher une contrepartie documentée ; sinon signaler \\
246+Financement avantageux & Balance de prix de vente, taux stipulé, reprise d'hypothèque & Ajuster (\cref{ch:17}) ou exclure si l'effet dépasse quelques points \\
247+Date de l'acte prise pour date de vente & Source sans date de promesse (registre, JLR, rôle) & Reconstituer la date de promesse ou reculer d'un délai documenté (\cref{eq:13:biais}) \\
248+Vente sans garantie légale & Mention à l'acte et sur la fiche descriptive (art.~118 du règlement de courtage) & Conserver avec un indicateur ; l'exclusion de garantie accompagne souvent une décote \\
249+\bottomrule
250+\end{tabularx}
251+\tabsource{synthèse de l'auteur d'après \textcite{ccq}, \textcite{ldmi}, \textcite{reglpropmed}, \textcite{reglCourtageDeontologie}, la méthodologie publiée de \textcite{jlr202609} et \textcite{appraisal2026}.}
252+\end{table}
253+
254+\subsection{Couverture et biais de composition des sources}
255+
256+Aucune source de transactions ne couvre tout le marché avec tous les champs. Le registre foncier est exhaustif mais pauvre en champs ; le courtage est riche mais partiel. Notons $N$ le nombre de ventes sincères publiées au registre sur un marché et une période, $N_c$ le nombre de ces ventes conclues par courtier et $N_a$ le nombre de ventes de la base de courtage qu'on a pu apparier à un acte du registre. Deux ratios décrivent la base : le taux de couverture $\kappa = N_c/N$ et le taux d'appariement $\alpha = N_a/N_c$. Le premier mesure la part du marché que la source riche observe ; le second mesure la qualité de la jointure entre les deux sources, qui repose sur des clés imparfaites (adresse, lot, date, prix). Si les ventes par courtier et les autres ventes n'ont pas le même prix moyen, parce que les propriétés vendues sans courtier sont systématiquement différentes ou que leur prix l'est, la moyenne du marché est une moyenne pondérée des deux :
257+\begin{equation}\label{eq:13:composition}
258+\mu = \kappa\,\mu_c + (1-\kappa)\,\mu_a ,
259+\end{equation}
260+et la statistique publiée à partir du seul courtage, $\mu_c$, s'écarte de $\mu$ de $(1-\kappa)(\mu_c - \mu_a)$. Ce biais de composition est inconnu tant que $\mu_a$ ne l'est pas, et il explique une partie des écarts entre les médianes de l'APCIQ et celles de JLR ; il ne disparaît pas en élargissant l'échantillon de courtage, puisqu'il tient à la sélection et non à la variance. Pour l'évaluateur qui compare un sujet à des ventes par courtier, il importe peu, parce que le sujet sera lui aussi vendu par courtier dans l'hypothèse la plus probable ; pour celui qui estime un niveau de marché ou un indice, il importe beaucoup, et les manuels internationaux sur les indices de prix des logements consacrent un chapitre à cette question des sources et de leur couverture \parencite{eurostat2013}. L'\cref{ex:13:6} applique l'\cref{eq:13:composition} à des chiffres fictifs.
261+
262+\begin{quebec}[{{Trois mesures du même marché : APCIQ, JLR et Teranet--Banque Nationale}}]
263+Pour la RMR de Québec, l'APCIQ publiait pour le troisième trimestre de 2026 un prix médian de \dollars{463500} pour l'unifamiliale, en hausse de 2~\% sur un an, sur \num{2161} ventes par courtier toutes catégories \parencite{apciqt32026} ; l'indice Teranet--Banque Nationale, qui ne mesure pas un niveau mais une variation à qualité constante par ventes répétées, affichait pour Québec une hausse de 7,7~\% sur un an en septembre 2026 \parencite{teranet202609} ; et JLR, qui compile toutes les ventes publiées, comptait pour septembre 2026 \num{579} ventes d'unifamiliales dans la RMR, en hausse de 6~\% \parencite{jlr202609}. Une variation de la médiane n'est pas une variation à qualité constante : si la composition des ventes change, par exemple si les ventes se déplacent vers des propriétés plus petites quand les taux montent, la médiane peut baisser alors que chaque propriété s'apprécie. Les trois chiffres ne se contredisent donc pas ; ils mesurent des choses différentes sur des univers différents, et le rapport doit nommer celui qu'il utilise. Le \cref{ch:35} explique pourquoi un indice hédonique ou de ventes répétées est préférable à une médiane pour l'ajustement de conditions du marché.
264+\end{quebec}
265+
266+\section{Les données de loyers}\label{sec:13:loyers}
267+
268+\subsection{L'Enquête sur les logements locatifs de la SCHL : univers, méthode, lecture}
269+
270+L'Enquête sur les logements locatifs (ELL) de la SCHL est menée chaque année au mois d'octobre auprès des propriétaires et gestionnaires d'immeubles locatifs. Son univers principal, tel que les tableaux le définissent, est celui des appartements d'initiative privée situés dans des immeubles d'au moins trois logements ; les maisons en rangée locatives font l'objet de tableaux distincts, et un volet séparé couvre les appartements en copropriété offerts en location \parencite{schlrmltables2025}. Pour chaque région métropolitaine, l'enquête publie l'univers (le nombre de logements dénombrés), le taux d'inoccupation, le loyer moyen, la variation estimative du loyer moyen et le taux de rotation, ventilés par zone géographique, par taille de logement (studio, une, deux, trois chambres et plus), par taille d'immeuble, par année de construction et par quartile de loyer. Le classeur de la RMR de Montréal pour 2025 compte trente-cinq tableaux et trente-neuf zones, dont les zones 7 (Villeray) et 9 (Rosemont) qui servent au dossier du cas 3 ; celui de la RMR de Gatineau distingue quatre zones (Hull, Aylmer, Gatineau, Périphérie) ; celui de la province, utilisé dans l'exemple intégral, donne les six RMR sans ventilation par zone pour la RMR de Québec.
271+
272+Trois conventions de lecture sont indispensables. La première concerne la qualité statistique : chaque estimation porte une lettre, de a (excellent) à d (passable, à utiliser avec prudence), et les estimations trop fragiles sont supprimées et remplacées par deux astérisques ; une flèche signale une variation annuelle statistiquement significative, un tiret une taille d'échantillon qui ne permet pas de l'interpréter. L'évaluateur qui reprend un loyer moyen de zone pour une taille de logement doit reprendre la lettre avec lui : un loyer coté d pour un studio dans une petite zone n'a pas la même force probante qu'un loyer coté a pour un quatre et demi à l'échelle de la RMR. La deuxième convention concerne la variation : la SCHL publie une « variation en pourcentage estimative » du loyer moyen, fondée sur les immeubles qui ont fait partie de l'échantillon les deux années, et cette mesure, qui neutralise les changements de composition de l'univers (livraison d'immeubles neufs plus chers, démolitions), n'est pas la variation brute qu'on obtient en divisant le loyer moyen d'une année par celui de l'année précédente. Notons $\bar L_t$ le loyer moyen publié et $\hat g_t$ la variation estimative ; la variation brute est
273+\begin{equation}\label{eq:13:brute}
274+g_t^{\text{brute}} = \frac{\bar L_t}{\bar L_{t-1}} - 1 ,
275+\end{equation}
276+et l'écart $g_t^{\text{brute}} - \hat g_t$ mesure l'effet de composition. Le \cref{tab:13:schl} montre que cet écart atteint plus de sept points à Montréal en 2025 : le loyer moyen des deux chambres y est passé de \dollars{1176} à \dollars{1346}, soit 14,5~\% de plus, alors que la hausse sur logements comparables était de 7,2~\%. L'évaluateur qui indexe un loyer de marché avec la variation brute surestime la hausse ; celui qui cherche le niveau des loyers des immeubles récents a besoin, au contraire, du loyer moyen par année de construction, pas de la variation estimative. La troisième convention concerne le sens du loyer moyen : c'est le loyer payé par les occupants en place, toutes anciennetés de bail confondues, et non le loyer demandé pour les logements vacants ; la SCHL publie d'ailleurs, dans ses tableaux 1.1.9, le loyer moyen des logements inoccupés et occupés séparément, et c'est le premier qui approche le loyer de marché au sens du \cref{ch:26}.
277+
278+Le taux d'inoccupation, enfin, rapporte les logements vacants et disponibles à la location à l'univers ; le nombre de logements vacants implicite dans une zone est donc
279+\begin{equation}\label{eq:13:vacants}
280+\mathit{Vac}_t = U_t \times v_t ,
281+\end{equation}
282+où $U_t$ est l'univers et $v_t$ le taux. L'\cref{eq:13:vacants} rappelle qu'un taux de 1~\% dans un univers de \num{20000} logements représente deux cents logements disponibles, ce qui est, pour un locataire qui cherche, un marché serré mais non vide, et que la variation du taux d'une année à l'autre est une variation de flux qui dépend des livraisons autant que de la demande ; le \cref{ch:27} en tire les conséquences pour l'hypothèse d'inoccupation du sujet.
283+
284+\begin{table}[tbp]
285+\caption{Enquête sur les logements locatifs de la SCHL, octobre 2025 : six RMR du Québec, appartements d'initiative privée dans les immeubles de trois logements et plus. Lettres : indicateur de qualité de la SCHL (a excellent … d passable).}\label{tab:13:schl}
286+\centering\footnotesize\setlength{\tabcolsep}{3.5pt}
287+\begin{tabular}{@{}l S[table-format=1.1] S[table-format=1.1] c S[table-format=4.0] S[table-format=4.0] c S[table-format=6.0] S[table-format=2.1] S[table-format=2.1] S[table-format=5.0]@{}}
288+\toprule
289+ & \multicolumn{3}{c}{Inoccupation, tous (\%)} & \multicolumn{3}{c}{Loyer moyen, 2 ch. (\$)} & {Univers} & \multicolumn{2}{c}{Var. loyer 2 ch. (\%)} & {Vacants} \\
290+\cmidrule(lr){2-4}\cmidrule(lr){5-7}\cmidrule(lr){9-10}
291+RMR & {2024} & {2025} & {Q} & {2024} & {2025} & {Q} & {tous, 2025} & {brute} & {estimative} & {implicites} \\
292+\midrule
293+Montréal & 2.1 & 2.9 & a & 1176 & 1346 & a & 667266 & 14.5 & 7.2 & 19351 \\
294+Québec & 0.9 & 2.4 & a & 1159 & 1277 & a & 118693 & 10.2 & 6.1 & 2849 \\
295+Gatineau (partie Québec) & 1.9 & 3.8 & c & 1353 & 1460 & a & 33553 & 7.9 & 4.7 & 1275 \\
296+Sherbrooke & 1.4 & 2.7 & a & 1002 & 1062 & a & 41949 & 6.0 & 9.8 & 1133 \\
297+Trois-Rivières & 0.9 & 2.7 & b & 885 & 1016 & a & 22505 & 14.8 & 6.6 & 608 \\
298+Saguenay & 1.6 & 1.3 & a & 882 & 985 & a & 16241 & 11.7 & 10.9 & 211 \\
299+\bottomrule
300+\end{tabular}
301+\tabsource{SCHL, Enquête sur les logements locatifs, octobre 2025, tableaux 1.1.1, 1.1.2, 1.1.3 et 1.1.5 du classeur \texttt{rmr-quebec-2025-fr.xlsx} (publié le 11 décembre 2025, consulté le 31 octobre 2026) ; variation brute et vacants implicites calculés par \texttt{analysis/python/cases/ch13\_sources.py}.}
302+\end{table}
303+
304+\begin{figure}[tbp]
305+\centering
306+\includegraphics[width=\linewidth]{ch13/ch13-fig-schl-loyers.pdf}
307+\caption{Enquête sur les logements locatifs de la SCHL, octobre 2025, six RMR du Québec : (a) loyer moyen des appartements d'initiative privée selon la taille du logement ; (b) taux d'inoccupation de tous les appartements en octobre 2024 et en octobre 2025. La hiérarchie des loyers (Gatineau et Montréal en tête, Saguenay en queue) et la détente générale de l'inoccupation en 2025, sauf à Saguenay, se lisent directement ; les lettres de qualité et la différence entre variation brute et estimative ne se lisent que dans les tableaux.}\label{fig:13:schl}
308+\figsource{SCHL, Enquête sur les logements locatifs, octobre 2025 (classeur \texttt{rmr-quebec-2025-fr.xlsx}, tableaux 1.1.1 et 1.1.2), consulté le 31 octobre 2026 ; script \texttt{analysis/python/figures/ch13\_schl.py}.}
309+\end{figure}
310+
311+\subsection{Le Tribunal administratif du logement : données de droit sur les loyers}
312+
313+Le Tribunal administratif du logement ne publie pas de loyers observés, mais trois de ses productions conditionnent la lecture de tout état locatif. La première est la liste annuelle des pourcentages applicables aux critères de fixation de loyer. Depuis le 1\textsuperscript{er} janvier 2026, pour les avis donnés à compter de cette date, la méthode a été refondue par le Règlement modifiant le Règlement sur les critères de fixation de loyer \parencite{decret1455_2025} : le pourcentage de base est désormais fondé sur l'indice des prix à la consommation moyen du Québec sur trois ans, et il s'y ajoute la variation des taxes municipales et scolaires, celle des primes d'assurance, un pourcentage de 5~\% des dépenses d'immobilisation et les dépenses d'un nouveau service, chacun selon la part attribuable au logement \parencite[art.~3 et 3.1]{reglCriteresFixation}. Pour 2026, le pourcentage de base est de 3,1~\% (4,5~\% en 2025) \parencite{talIndices2026}. Pour les avis donnés avant le 1\textsuperscript{er} janvier 2026, l'ancienne méthode continue de s'appliquer, avec ses pourcentages par poste de dépenses (entretien 5,7~\%, gestion 7,2~\%, revenu net 5,7~\%, immobilisations 4~\% en 2026). Le \cref{ch:34} en détaille le calcul ; ce qui importe ici est que le loyer d'un logement occupé n'est pas une donnée de marché mais le résultat d'une trajectoire administrée depuis le premier bail, de sorte que l'écart entre loyer contractuel et loyer de marché est une variable d'état de l'immeuble, que l'évaluateur doit mesurer logement par logement (\cref{ch:26,ch:33}).
314+
315+La deuxième production est le formulaire de renseignements nécessaires à la fixation du loyer (RN), que le locateur doit produire lorsque le locataire refuse une modification et que le locateur saisit le tribunal. Sa version 2026-2027 recense les revenus de décembre 2025 multipliés par douze, les autres revenus de 2025, les taxes municipales de 2026 et de 2025, les taxes scolaires des deux dernières années, les primes d'assurance en vigueur au 31 décembre 2025 et 2024, les dépenses d'énergie, d'entretien et de services de 2025, les frais de gestion s'ils dépassent 5~\% des revenus et les dépenses d'immobilisation de 2025 par ligne avec le nombre de logements concernés \parencite{talFormulaireRN2026}. Ce formulaire est, de fait, le seul état des revenus et dépenses normalisé qui existe au Québec pour les immeubles locatifs résidentiels, et sa structure est un excellent gabarit pour la collecte des dépenses d'exploitation du \cref{ch:28}, à ceci près que ses postes sont définis aux fins de la fixation et non de l'évaluation : les dépenses d'immobilisation y sont amorties selon un pourcentage réglementaire, non selon leur durée de vie économique.
316+
317+La troisième production est la jurisprudence résumée. Le tribunal rappelle qu'en fixation de loyer d'un nouveau locataire, « la valeur marchande locative n'est pas un critère à prendre en considération » \parencite{talResumesDecisions}, ce qui signifie que le loyer de marché que l'évaluateur estime et le loyer que le tribunal fixerait sont deux grandeurs distinctes ; qu'une dépense d'immobilisation ne peut être prise en considération qu'une seule fois ; et que le locateur d'un immeuble de moins de cinq ans peut se prévaloir de l'exclusion de l'art.~1955 du Code civil, à condition, depuis le 21 février 2024, que le bail indique le loyer maximal exigible pendant ces cinq années \parencite[art.~1955]{ccq}. Deux autres règles du Code civil font du bail lui-même une source de données : le locateur doit indiquer au nouveau locataire, dans la section prévue du formulaire obligatoire de bail, le loyer le plus bas payé au cours des douze mois précédents \parencite[art.~1896]{ccq}, et il ne peut exiger aucune somme autre que le loyer, sous forme de dépôt ou autrement \parencite[art.~1904]{ccq}. La première règle fait de chaque bail une observation de l'historique du loyer, que l'évaluateur consulte dans le dossier du propriétaire ; la seconde fait du loyer stipulé la totalité de la contrepartie, ce qui simplifie sa comparaison.
318+
319+\begin{quebec}[{{Quand la donnée statistique devient une règle de droit : le moratoire sur les évictions}}]
320+La Loi visant principalement à prolonger la durée des baux de logement et à protéger les locataires aînés et les locataires vulnérables prévoit qu'aucun locataire ne peut être évincé aux fins de l'art.~1959 du Code civil (subdivision, agrandissement, changement d'affectation) avant le 6 juin 2027, et qu'il peut être mis fin au moratoire par avis ministériel, soixante jours après sa publication, si le taux d'inoccupation mesuré par la SCHL dans les centres de 10~000 habitants et plus atteint 3~\% \parencite[art.~1 et 11]{loi65_2024}. Le taux d'inoccupation de l'Enquête sur les logements locatifs n'est donc plus seulement une donnée d'analyse : c'est le déclencheur d'une règle qui modifie la valeur de tout immeuble dont la plus haute et meilleure utilisation passe par un changement d'affectation (\cref{ch:34,ch:41}). Au 31 octobre 2026, aucun avis de fin anticipée n'avait été repéré ; les taux d'octobre 2025 du \cref{tab:13:schl} (2,9~\% à Montréal, 3,8~\% à Gatineau, 2,4~\% à Québec) montrent que la mesure provinciale s'en approchait. L'évaluateur qui travaille sur un scénario de conversion doit suivre cette donnée et cette loi ensemble.
321+\end{quebec}
322+
323+\subsection{Loyers d'annonces, loyers contractuels et loyers de marché}
324+
325+Trois notions de loyer coexistent et les sources ne mesurent pas la même. Le \motcle{loyer contractuel} est celui que stipule le bail en cours ; c'est la donnée de l'état locatif que le propriétaire remet à l'évaluateur, et que les déclarations du vendeur recueillent lors d'une vente. Le \motcle{loyer de marché} est celui qu'un logement vacant obtiendrait aujourd'hui d'un locataire informé après une exposition normale ; il n'est observé que lors d'une relocation. Le \motcle{loyer d'annonce} est celui que demande un locateur pour un logement offert ; il est observable en masse sur les plateformes d'annonces, mais il est un prix demandé et non un prix convenu, il surreprésente les logements qui tardent à se louer et il ne dit rien des concessions consenties. La SCHL mesure principalement des loyers contractuels (les loyers payés par les occupants), avec un tableau de loyers des logements inoccupés qui approche le loyer de marché ; l'IPC mesure la variation des loyers payés par un échantillon de ménages (composante « loyer », en hausse de 3,3~\% au Québec entre septembre 2025 et septembre 2026 d'après le tableau 18-10-0004-01) ; les annonces mesurent des loyers demandés. Dans un marché encadré, où la trajectoire du loyer contractuel est bornée par la méthode de fixation et le droit au maintien dans les lieux, l'écart entre les trois notions est grand et persistant, et la recension de \textcite{kholodilin2024} rappelle que les loyers « observés » dans un parc encadré ne reflètent le marché que pour les logements qui viennent d'être reloués. L'évaluateur dit donc toujours quel loyer il mesure, et le dossier du cas 3 (\cref{ch:47}) distingue explicitement, pour chacun des douze logements, le loyer contractuel et le loyer de marché estimé.
326+
327+\section{Les données de coûts}\label{sec:13:couts}
328+
329+\subsection{Lire le tableau 18-10-0289-01 dans son fichier brut}
330+
331+L'Indice des prix de la construction de bâtiments est la source publique de référence pour transporter un coût de construction dans le temps, et le \cref{ch:21} l'a utilisé pour l'indexation. Il faut ici en comprendre la structure telle qu'elle apparaît dans le fichier téléchargé, parce que c'est ce fichier, et non la page Web, qu'une chaîne de données reproductible exploite. Statistique Canada diffuse chaque tableau sous la forme d'une archive contenant un fichier de données et un fichier de métadonnées. Le fichier de données du tableau 18-10-0289-01, déposé dans le dépôt de l'ouvrage, compte \num{318954} lignes et seize colonnes ; chaque ligne est une observation d'une série à une période, identifiée par sa période de référence, sa géographie, le type d'immeuble, la division de travaux, l'unité de mesure, le vecteur (identifiant permanent de la série, de la forme v1617911460), les coordonnées dans le cube et la valeur. Le \cref{tab:13:ipcb} en résume les dimensions, lues par script dans le fichier : vingt-cinq géographies, seize types d'immeubles et vingt-quatre divisions, soit \num{9600} croisements possibles dont \num{8750} sont effectivement publiés, sur 183 trimestres du premier trimestre de 1981 au troisième de 2026, sans aucune valeur manquante, l'absence d'une série se traduisant par l'absence de lignes et non par des cellules vides.
332+
333+\begin{table}[tbp]
334+\caption{Structure du fichier brut du tableau 18-10-0289-01 de Statistique Canada (IPCB), telle que lue par script le 31 octobre 2026}\label{tab:13:ipcb}
335+\centering\small
336+\begin{tabularx}{\textwidth}{@{}L{44mm}Y@{}}
337+\toprule
338+Élément & Contenu \\
339+\midrule
340+Fichier & \texttt{18100289.csv} (58,1 Mo), séparateur point-virgule, encodage UTF-8 avec marque d'ordre ; \texttt{18100289\_MetaData.csv} (dimensions, membres, notes) \\
341+Lignes et colonnes & \num{318954} observations ; 16 colonnes (période de référence, géographie, DGUID, type d'immeuble, division, unité de mesure et son identifiant, facteur scalaire et son identifiant, vecteur, coordonnées, valeur, statut, symbole, terminé, décimales) \\
342+Dimension 1 : géographie & 25 membres : composite de quinze RMR, provinces et RMR ; pour le Québec, la province, Montréal et Québec \\
343+Dimension 2 : type d'immeuble & 16 membres : bâtiments résidentiels (immeubles d'appartements : tours de cinq étages ou plus, immeubles à hauteur restreinte ; maison individuelle ; maison en rangée) et non résidentiels (commerciaux : bureaux, entrepôt, centre commercial ; industriels : usine, gare d'autobus ; institutionnels : école) \\
344+Dimension 3 : division & 24 membres : agrégat des divisions et 23 divisions de travaux (exigences générales, démolition, béton, maçonnerie, charpente en acier, ouvrages métalliques, bois, isolation, ouvertures, revêtements, ouvrages spéciaux, équipement, systèmes transporteurs, incendie, plomberie, CVCA, automatisation, électricité, communications, sécurité, terrassements, aménagements extérieurs, services d'utilités) \\
345+Périodes & Trimestrielles, de 1981-01 à 2026-07 (183 périodes) ; la période est codée par le premier mois du trimestre \\
346+Séries & \num{8750} vecteurs ; unité « Indice, 2023 = 100 » ; aucune valeur manquante (une série absente n'a pas de ligne) \\
347+Débuts de série à Montréal & Non résidentiel : 1981 ; tours d'habitation : 1988 ; autres séries résidentielles : 2017 ; RMR de Québec, résidentiel : 2023 \\
348+Dernière diffusion & 29 octobre 2026 (données du troisième trimestre de 2026) \\
349+\bottomrule
350+\end{tabularx}
351+\tabsource{Statistique Canada, tableau 18-10-0289-01, fichiers bruts \texttt{data/raw/statcan\_18100289/} ; lecture par \texttt{analysis/python/cases/ch13\_sources.py}.}
352+\end{table}
353+
354+Deux enseignements pratiques découlent de cette lecture. Le premier est que les séries résidentielles sont récentes : pour Montréal, elles ne commencent qu'au premier trimestre de 2017 (à l'exception des tours d'habitation, depuis 1988), et pour la RMR de Québec, ajoutée au tableau, elles ne commencent qu'au premier trimestre de 2023, avec quinze observations au troisième trimestre de 2026. Aucune indexation d'un coût résidentiel québécois antérieur à 2017 n'est donc possible avec cette source, et l'indexation d'un coût de la région de Québec antérieur à 2023 exige un substitut, Montréal ou le composite des quinze RMR, dont il faut déclarer l'emploi. Le second enseignement est que le tableau 18-10-0276-01, longtemps cité dans les rapports, est archivé depuis le deuxième trimestre de 2024 et ne doit plus être utilisé pour les valeurs récentes \parencite{statcanipcb0276} ; le raccordement de deux séries par une période commune, exposé au \cref{ch:21}, s'impose lorsqu'un coût historique a été indexé avec l'ancien tableau.
355+
356+\subsection{Coût, prix et prix à la consommation : trois indices qu'on confond}
357+
358+La confusion la plus répandue dans les rapports consiste à indexer un coût de construction avec l'Indice des prix des logements neufs ou avec l'Indice des prix à la consommation. La \cref{fig:13:indices} montre pourquoi c'est une erreur grave. Rebasés au premier trimestre de 2017, les trois indices divergent fortement : au troisième trimestre de 2026, l'IPCB des bâtiments résidentiels à Montréal atteint 179,8, l'IPLN de Montréal (maison et terrain) 159,0 et l'IPC d'ensemble du Québec 132,2. Un coût de construction de 2017 transporté en 2026 avec l'IPC serait sous-estimé d'environ un quart ; transporté avec l'IPLN, d'un peu plus d'un dixième. La raison tient à ce que chaque indice mesure : l'IPCB suit le prix que les entrepreneurs facturent pour un ensemble fixe de bâtiments types, matériaux, main-d'œuvre, équipement, frais généraux et profit compris ; l'IPLN suit le prix de vente que les constructeurs obtiennent pour des maisons neuves, terrain compris dans la série totale, et il reflète donc la demande et le prix des terrains autant que les coûts ; l'IPC suit un panier de consommation des ménages dont le logement n'est qu'une composante, elle-même mesurée par le coût d'usage et non par le prix des immeubles. Le rebasage lui-même est une opération élémentaire mais qu'il faut écrire :
359+\begin{equation}\label{eq:13:rebasage}
360+I_t^{(b)} = 100\,\frac{I_t}{I_b} ,
361+\end{equation}
362+où $b$ est la période choisie pour base ; le ratio d'indexation $I_t/I_0$ est invariant au rebasage, de sorte que la base publiée (2023 = 100 pour l'IPCB, décembre 2016 = 100 pour l'IPLN, 2002 = 100 pour l'IPC) n'a aucune incidence sur les calculs, et que seuls le choix de l'indice et celui des périodes comptent. Lorsque la série est mensuelle et le coût trimestriel, ou l'inverse, la moyenne des trois mois du trimestre est la convention retenue dans l'ouvrage ; elle est déclarée dans le script qui produit la figure.
363+
364+\begin{figure}[tbp]
365+\centering
366+\includegraphics[width=\linewidth]{ch13/ch13-fig-indices.pdf}
367+\caption{Trois indices souvent confondus, rebasés au premier trimestre de 2017 : l'Indice des prix de la construction de bâtiments (Montréal, bâtiments résidentiels), l'Indice des prix des logements neufs (Montréal, maison et terrain) et l'Indice des prix à la consommation (Québec, ensemble). Au troisième trimestre de 2026, le coût de construction a progressé de près de 80~\%, le prix des maisons neuves de 59~\% et les prix à la consommation de 32~\%. Indexer un coût avec l'un des deux derniers indices le sous-estime gravement.}\label{fig:13:indices}
368+\figsource{Statistique Canada, tableaux 18-10-0289-01 (diffusé le 29 octobre 2026), 18-10-0205-01 (22 octobre 2026) et 18-10-0004-01 (19 octobre 2026), consultés le 31 octobre 2026 ; moyennes trimestrielles des séries mensuelles ; script \texttt{analysis/python/figures/ch13\_indices.py}.}
369+\end{figure}
370+
371+\subsection{Guides commerciaux, permis, conventions collectives et Manuel}
372+
373+Les sources de coûts unitaires sont, pour l'essentiel, des données d'industrie sous licence. Le \emph{Canadian Cost Guide} d'Altus Group, publié le 23 avril 2026 pour son édition courante, donne des fourchettes de coûts au pied carré par type de bâtiment et par ville canadienne, dont Montréal, dans des tableaux réservés aux abonnés \parencite{altus2026,altuscostinsights2026} ; le \emph{Residential Cost Handbook} de Marshall \& Swift et les données RSMeans de Gordian fournissent des coûts par classe de qualité, des prix unitaires par poste et des multiplicateurs locaux et temporels \parencite{marshallswift2025,rsmeans2025}. Le \cref{ch:21} a exposé comment on s'en sert et l'ouvrage n'en reproduit aucun extrait ; on rappellera seulement que la seule fourchette publique dont dispose le lecteur sans licence, 155 à 215~\$ le pied carré pour une unifamiliale à Montréal, provient d'une source secondaire et doit être tenue pour partiellement vérifiée. Les permis de construction des municipalités ouvertes donnent des coûts déclarés par les requérants, sans valeur légale et sans définition homogène de ce qu'ils incluent \parencite{mtlpermis} ; ils servent à dater et à qualifier des travaux, non à les chiffrer. Les conventions collectives de l'industrie de la construction, administrées par la Commission de la construction du Québec, fixent les salaires par métier et par secteur ; la convention 2025-2029 prévoit des hausses de 8, 5, 5 et 4~\% sur quatre ans \parencite{ccqSalaires}, et c'est la seule composante du coût direct dont le prix soit public et contractuel. Le Manuel d'évaluation foncière, enfin, contient dans sa partie 3E les barèmes de coûts que les évaluateurs municipaux doivent appliquer ; il est librement téléchargeable mais protégé par le droit d'auteur du gouvernement du Québec, et ses barèmes expriment un coût de remplacement à la date de référence du rôle, selon les conventions de mesure de la partie 2C, ce qui en limite l'usage direct dans l'évaluation privée \parencite{mefq2026,mefqpartie2}.
374+
375+\section{Constituer une base d'évaluation}\label{sec:13:base}
376+
377+\subsection{La chaîne de données}
378+
379+Les sections précédentes ont décrit des sources hétérogènes par leur contenu, leur unité d'observation, leur date, leur licence et leur format. Les combiner dans un dossier exige une chaîne d'opérations que la \cref{fig:13:chaine} schématise et que le \cref{ch:14} détaille pour les étapes de nettoyage et de géocodage. La chaîne compte six maillons. L'\emph{acquisition} télécharge ou extrait chaque source et consigne, pour chaque fichier, l'adresse, la date, la version, la licence et le responsable, dans un fichier de sources analogue à celui du dépôt de l'ouvrage ; un fichier brut n'est jamais modifié, de sorte que tout traitement puisse être rejoué. La \emph{normalisation} ramène les unités (mètres carrés, dollars courants, dates ISO), les codages (type de propriété, utilisation CUBF, taille de logement) et les identifiants (adresse civique normalisée, numéro de lot, matricule) à des conventions communes et déclarées. L'\emph{appariement} relie les enregistrements d'une même propriété entre les sources par des clés : le matricule et l'adresse pour le rôle, le lot et l'adresse pour le registre, l'adresse et la date pour le courtage ; il produit une base à trois niveaux, la propriété, la transaction et le bail, et il mesure son propre taux de réussite. L'\emph{épuration} applique les règles du \cref{tab:13:pieges} et les règles statistiques du \cref{ch:14}, et elle tient le \emph{journal des exclusions}, qui conserve chaque enregistrement écarté avec le motif, parce qu'une exclusion est une décision d'évaluation et qu'elle doit pouvoir être défendue. L'\emph{enrichissement} ajoute les variables dérivées (âge, prix unitaire, ratio prix/rôle, distance au centre, indicateurs de voisinage) et les séries de contexte (indices, taux, loyers de zone). La \emph{documentation}, enfin, produit le dictionnaire des variables, les métadonnées et les tests de cohérence qui accompagnent la base.
380+
381+\begin{figure}[tbp]
382+\centering
383+\begin{tikzpicture}[
384+ node distance=3mm and 6mm, font=\sffamily\footnotesize,
385+ src/.style={draw=brandGray, fill=white, rounded corners=1pt, text width=27mm, minimum height=8mm, align=center, line width=0.5pt},
386+ etape/.style={draw=brandPrimary, fill=brandLight, rounded corners=1pt, text width=30mm, minimum height=9mm, align=center, line width=0.6pt},
387+ base/.style={draw=brandPrimary, fill=brandPrimary, text=white, rounded corners=1pt, text width=30mm, minimum height=9mm, align=center},
388+ usage/.style={draw=brandDark, fill=white, rounded corners=1pt, text width=27mm, minimum height=8mm, align=center, line width=0.5pt},
389+ meta/.style={draw=brandAmber, dashed, fill=orange!5!white, rounded corners=1pt, text width=30mm, align=center, font=\sffamily\scriptsize},
390+ fl/.style={-{Stealth[length=1.8mm]}, brandGray, line width=0.6pt}]
391+ \node[src] (s1) {Registre foncier\\ actes, index [O]};
392+ \node[src, below=of s1] (s2) {Rôle d'évaluation\\ XML MAMH [O]};
393+ \node[src, below=of s2] (s3) {Courtage\\ fiches, DV [I]};
394+ \node[src, below=of s3] (s4) {SCHL, TAL\\ loyers, règles [O]};
395+ \node[src, below=of s4] (s5) {Statistique Canada,\\ BdC : indices, taux [O]};
396+ \node[src, below=of s5] (s6) {Guides de coûts,\\ CBRE, JLR [I]};
397+ \node[etape, right=of s2] (acq) {1. Acquisition\\ URL, date, version, licence};
398+ \node[etape, below=of acq] (norm) {2. Normalisation\\ unités, codes, adresses};
399+ \node[etape, below=of norm] (app) {3. Appariement\\ matricule, lot, adresse, date};
400+ \node[etape, below=of app] (epu) {4. Épuration\\ règles, statistiques};
401+ \node[base, right=of app] (base) {Base d'évaluation\\ propriétés, transactions,\\ baux, indices};
402+ \node[meta, above=of base] (jour) {Journal des exclusions\\ motif, source, date};
403+ \node[meta, below=of base] (dic) {Dictionnaire des variables,\\ métadonnées, tests};
404+ \node[usage, right=of base] (u2) {Comparaison, hédonique\\ (ch.~16 à 20)};
405+ \node[usage, above=of u2] (u1) {Étude de marché\\ (ch.~15)};
406+ \node[usage, below=of u2] (u3) {Revenu, coût, indices\\ (ch.~21 à 35)};
407+ \foreach \s in {s1,s2,s3,s4,s5,s6} \draw[fl] (\s.east) -- ++(3mm,0) |- (acq.west);
408+ \draw[fl] (acq) -- (norm); \draw[fl] (norm) -- (app); \draw[fl] (app) -- (epu);
409+ \draw[fl] (epu.east) -- ++(3mm,0) |- (base.west);
410+ \draw[fl, dashed, brandAmber] (epu.north east) -- (jour.west);
411+ \draw[fl, dashed, brandAmber] (base) -- (dic);
412+ \foreach \u in {u1,u2,u3} \draw[fl] (base.east) -- (\u.west);
413+\end{tikzpicture}
414+\caption{La chaîne de données de l'évaluation : des sources officielles [O] et d'industrie [I] à la base d'évaluation, en passant par l'acquisition documentée, la normalisation, l'appariement et l'épuration. Le journal des exclusions et le dictionnaire des variables sont des produits de la chaîne au même titre que la base.}\label{fig:13:chaine}
415+\figsource{schéma de l'auteur (TikZ, \texttt{manuscript/chapters/ch13.tex}).}
416+\end{figure}
417+
418+\subsection{Le schéma de la base et ses clés}
419+
420+Une base d'évaluation résidentielle se décrit par cinq tables reliées par des clés, dont le \cref{tab:13:schema} donne la structure minimale. La table des \emph{propriétés} a pour clé un identifiant interne stable et porte les caractéristiques qui ne changent pas d'une transaction à l'autre ou qui changent lentement : localisation, terrain, bâtiment, logements, utilisation, coordonnées. La table des \emph{transactions} a pour clé l'identifiant de la vente et pour clé étrangère l'identifiant de la propriété ; elle porte le prix, les dates (promesse, acte, inscription), les conditions, les meubles, le financement, la source de chaque champ et les indicateurs d'épuration. La table des \emph{baux} relie un logement à ses loyers successifs avec les dates de début et de fin. La table des \emph{valeurs au rôle} conserve, pour chaque rôle triennal, les valeurs du terrain, du bâtiment et de l'immeuble avec la date de référence, parce que la valeur au rôle change tous les trois ans et que le ratio prix/rôle doit être calculé avec le rôle en vigueur à la date de la vente. La table des \emph{séries de contexte} contient les indices, les taux et les statistiques de zone par période. Trois clés d'appariement se partagent le travail. Le \motcle{matricule} du rôle, composé de la division, de la section et de l'emplacement, est l'identifiant municipal de l'unité d'évaluation ; il figure dans les fichiers ouverts du rôle et sur l'avis d'évaluation, mais pas dans les actes. Le \motcle{numéro de lot} du cadastre du Québec identifie l'immeuble dans les actes et à l'index des immeubles ; il est caviardé dans le rôle ouvert et absent des fiches de courtage la plupart du temps. L'\motcle{adresse civique normalisée} est la seule clé commune aux trois sources, et c'est la moins fiable : numéros inférieur et supérieur, fractions, appartements, génériques et liens de voie suivent des conventions différentes, et le \cref{ch:14} consacre une section à leur normalisation et au géocodage. Le taux d'appariement $\alpha$ de l'\cref{eq:13:composition} se mesure à chaque jointure et se rapporte ; un taux de 85 à 95~\% est ordinaire pour une jointure par adresse entre le rôle et le courtage en milieu urbain, et les enregistrements non appariés doivent être examinés, non supprimés en silence.
421+
422+\begin{table}[tbp]
423+\caption{Schéma minimal d'une base d'évaluation résidentielle : tables, clés et sources des champs}\label{tab:13:schema}
424+\centering\footnotesize
425+\begin{tabularx}{\textwidth}{@{}L{22mm}L{26mm}YL{30mm}@{}}
426+\toprule
427+Table & Clé primaire (clés étrangères) & Champs principaux & Source des champs \\
428+\midrule
429+Propriétés & id\_propriete (matricule, lot, adresse normalisée) & adresse, coordonnées, secteur, type, superficie du terrain, aire d'étages, année de construction (réelle ou estimée), logements, utilisation (CUBF), unité de voisinage & Rôle ouvert (MAMH) ; registre pour le lot ; géocodage (\cref{ch:14}) \\
430+Transactions & id\_vente (id\_propriete) & prix d'acte, meubles, prix retenu, date de promesse, date d'acte, date d'inscription, vendeur et acheteur codés (type, lien), garantie, financement, source, indicateurs d'épuration, motif d'exclusion & Registre foncier ; fiche de courtage ; JLR \\
431+Baux et loyers & id\_bail (id\_propriete, logement) & logement, pièces, superficie, loyer contractuel, début et fin, inclusions, loyer de marché estimé, source & État locatif du propriétaire ; déclarations du vendeur (D2.6) ; bail (section du loyer antérieur) \\
432+Valeurs au rôle & id\_propriete, rôle (millésime) & valeur du terrain, du bâtiment, de l'immeuble, valeur au rôle antérieur, date de référence du marché, facteur comparatif & Rôle ouvert (RL0401A à RL0405A) ; proportion médiane (Québec.ca) \\
433+Séries de contexte & géographie, période & IPCB, IPLN, IPC, taux directeur et hypothécaires, loyers et inoccupation SCHL par zone, médianes APCIQ et JLR, pourcentages TAL & Statistique Canada ; Banque du Canada ; SCHL ; APCIQ ; JLR ; TAL \\
434+\bottomrule
435+\end{tabularx}
436+\tabsource{conception de l'auteur ; les codes RL renvoient au Répertoire des renseignements prescrits du rôle, version 2.5 \parencite{dqrepertoire}.}
437+\end{table}
438+
439+\subsection{Métadonnées, versions et reproductibilité}
440+
441+Une base n'est utilisable dans un rapport que si l'on peut dire, pour chaque chiffre qui en sort, d'où il vient. Cela impose trois disciplines. La première est le \emph{fichier de sources} : une ligne par fichier brut avec l'adresse exacte, la date de téléchargement, la taille, la licence et la description, tenue à jour à chaque acquisition ; le fichier \texttt{data/raw/SOURCES.md} du dépôt de l'ouvrage en est un modèle, et le \cref{tab:13:acces} en est la version lisible. La deuxième est la \emph{traçabilité des champs} : chaque champ de la base porte sa source, et chaque valeur transformée porte la règle qui l'a produite ; lorsqu'un prix de vente a été diminué d'une contrepartie pour meubles ou qu'une date a été reculée d'un délai typique, la valeur d'origine et la règle sont conservées. La troisième est le \emph{versionnage} : la base est datée, les scripts qui la produisent sont conservés avec elle, et une base recalculée après l'ajout de nouvelles ventes porte une nouvelle version, parce que les indices qui en sortent seront révisés et que le rapport qui a utilisé la version antérieure doit rester reproductible. \textcite{krauseLipscomb2016} ont proposé pour la préparation des données immobilières un protocole en étapes documentées qui va dans ce sens ; les normes de l'évaluation de masse de l'IAAO exigent de même une documentation de la collecte, de la validation des ventes et de la tenue à jour des données \parencite{iaaomass2025}, et le Règlement sur le rôle impose aux organismes municipaux de vérifier les données de chaque unité au moins tous les neuf ans \parencite[art.~36.1]{lfm}. L'évaluateur privé n'est pas tenu à ces normes, mais le rapport qu'il signe doit décrire l'étendue de ses recherches et le traitement de l'information \parencite[règle 2.3]{oeaqNormes}, et une base documentée est la seule façon de le faire sans réécrire la méthode à chaque dossier.
442+
443+\section{Exemple intégral : lire quatre fichiers réels par script}\label{sec:13:exemple}
444+
445+L'exemple qui suit n'utilise aucune donnée synthétique. Il lit, par le script \texttt{analysis/python/cases/ch13\_sources.py}, quatre fichiers réels déposés dans \texttt{data/raw/} avec leur adresse, leur date et leur licence (\texttt{data/raw/SOURCES.md}), et il en tire les chiffres cités dans ce chapitre ; le test \texttt{analysis/python/tests/test\_ch13.py} vérifie chacun d'eux. L'objectif pédagogique est double : montrer qu'une source officielle se lit dans son fichier brut, sans intermédiaire, et montrer ce que la lecture révèle qu'une page Web ne dit pas.
446+
447+\begin{exemple}{Lecture de l'IPCB, du dictionnaire du rôle, de la richesse foncière uniformisée et de l'Enquête sur les logements locatifs}{13:lecture}
448+\emph{Étape 1 : le tableau 18-10-0289-01 de Statistique Canada.} Le fichier \texttt{18100289.csv} est lu avec son séparateur point-virgule et son encodage ; ses \num{318954} lignes sont décrites au \cref{tab:13:ipcb}. La série des bâtiments résidentiels de Montréal, agrégat des divisions (vecteur v1617911460), compte 39 observations du premier trimestre de 2017 au troisième trimestre de 2026 ; elle passe de 65,5 à 117,8, soit un facteur de 1,798, et sa variation sur un an au troisième trimestre de 2026 est de 8,57~\% (108,5 à 117,8). La série équivalente de la RMR de Québec (vecteur v1617911076) ne compte que 15 observations, du premier trimestre de 2023 (98,8) au troisième trimestre de 2026 (124,8), soit un facteur de 1,263 sur quatorze trimestres et une variation annuelle de 9,19~\% ; sur la même période, Montréal passe de 99,9 à 117,8, un facteur de 1,179. Les coûts de construction résidentielle ont donc progressé plus vite à Québec qu'à Montréal depuis 2023, ce qu'un rapport qui indexerait un coût de Québec avec la série de Montréal sous-estimerait d'environ sept points. Le composite des quinze RMR, enfin, ne progresse que de 2,67~\% sur un an au troisième trimestre de 2026, contre 8,6 et 9,2~\% pour les deux RMR québécoises : la hausse récente est un phénomène québécois, non canadien, ce qui rend d'autant plus fautif l'usage d'une série nationale.
449+
450+\emph{Étape 2 : le dictionnaire du rôle en format ouvert.} Le Répertoire des renseignements prescrits du rôle d'évaluation foncière, version 2.5, est un document de deux pages daté du 24 mars 2022 \parencite{dqrepertoire}. Le script en extrait le texte et y dénombre 62 codes de renseignement distincts, répartis en cinq sections : identification de l'unité d'évaluation (21 codes, dont les dix composantes de l'adresse RL0101Ax à RL0101Jx, le matricule RL0104A à RL0104H, le code d'utilisation prédominante RL0105A et le numéro d'unité de voisinage RL0107A), propriétaire (2 codes conservés après caviardage : date initiale d'inscription et statut aux fins scolaires), caractéristiques de l'unité (17 codes, dont la superficie du terrain RL0302A, l'année de construction RL0307A et son caractère réel ou estimé RL0307B, l'aire d'étages RL0308A et le nombre de logements RL0311A), valeurs au rôle (5 codes : date des conditions du marché RL0401A, valeur du terrain RL0402A, du bâtiment RL0403A, de l'immeuble RL0404A et valeur au rôle antérieur RL0405A) et répartition fiscale (17 codes, dont la mention de terrain vague desservi RL0501A). Vingt-quatre codes portent le suffixe « x », qui signale un renseignement répétitif ; le \cref{tab:13:dico} en donne une sélection avec le type et la taille prescrits. L'index des rôles 2026 compte 1~134 lignes (code géographique, nom du territoire, adresse du fichier XML), et l'adresse du rôle de Gatineau y est \texttt{RL81017\_2026.xml}. Cette lecture dit trois choses au concepteur de base : la section 2 ne permet aucune identification du propriétaire ; l'aire d'étages et l'année de construction sont des renseignements facultatifs (colonne « obligatoirement présent » à N), de sorte qu'ils peuvent manquer ; et les valeurs du terrain et du bâtiment sont elles aussi facultatives, seule la valeur de l'immeuble étant obligatoire.
451+
452+\emph{Étape 3 : la richesse foncière uniformisée 2026.} Le fichier \texttt{RFU-2026.csv} compte 1~132 lignes, toutes des municipalités locales, et 37 colonnes dont 24 postes décrits dans le dictionnaire qui l'accompagne ; le dictionnaire donne pour chaque poste l'équation qui le définit à partir des postes du sommaire du rôle, et le poste CIALX02140 (richesse foncière uniformisée) y est explicitement la somme de treize postes. Pour les trois villes des dossiers de cas, l'évaluation imposable uniformisée s'élève à 430,75 milliards de dollars à Montréal, 105,22 à Québec et 60,01 à Gatineau, et le facteur de redressement (CSALX02163) y vaut respectivement 1,00, 1,08 et 1,02 : ce sont exactement les facteurs comparatifs que ces villes publient pour les droits de mutation de 2026, ce qui fournit un recoupement indépendant de la \cref{eq:13:ldmi}. La composition par catégorie, à la \cref{fig:13:rfu}, oppose Gatineau, où les résidences d'un logement font 56,5~\% de l'assiette, à Montréal, où les immeubles à logements et les copropriétés en font 60,6~\% ; Québec est intermédiaire (40,2 et 38,8~\%). La somme des six postes par catégorie est égale, au dollar près, à l'évaluation imposable uniformisée, ce qui est une vérification élémentaire mais indispensable de la lecture. Les trois villes représentent 31,1~\% de la richesse foncière uniformisée de l'ensemble des municipalités locales, qui s'élève à 2~078 milliards de dollars.
453+
454+\emph{Étape 4 : l'Enquête sur les logements locatifs.} Le classeur provincial \texttt{rmr-quebec-2025-fr.xlsx} compte vingt onglets ; le script y lit, pour les six RMR, les tableaux 1.1.1 (inoccupation), 1.1.2 (loyer moyen), 1.1.3 (univers) et 1.1.5 (variation estimative), en repérant la ligne d'en-tête et en conservant la lettre de qualité associée à chaque valeur. Le \cref{tab:13:schl} en résulte. Deux lectures retiennent l'attention. L'univers montréalais de \num{667266} appartements et un taux d'inoccupation de 2,9~\% impliquent, par l'\cref{eq:13:vacants}, environ \num{19351} logements vacants ; celui de Saguenay, \num{16241} appartements à 1,3~\%, en implique 211. Et la variation brute du loyer moyen des deux chambres dépasse la variation estimative dans cinq RMR sur six (14,5 contre 7,2 points à Montréal, 14,8 contre 6,6 à Trois-Rivières), l'exception étant Sherbrooke, où la variation estimative (9,8~\%) dépasse la variation brute (6,0~\%) : l'effet de composition n'a pas de signe fixe, et seule la variation estimative mesure l'évolution du marché à logement constant.
455+\end{exemple}
456+
457+\begin{table}[tbp]
458+\caption{Extrait du Répertoire des renseignements prescrits du rôle d'évaluation foncière en format ouvert (version 2.5) : renseignements les plus utiles à l'évaluateur}\label{tab:13:dico}
459+\centering\footnotesize
460+\begin{tabular}{@{}l L{72mm} c l c@{}}
461+\toprule
462+Code & Renseignement & Obligatoire & Type et taille & Section \\
463+\midrule
464+RL0101Gx & Nom de la voie publique de l'adresse & O & caractère 75 & 1 \\
465+RL0104A--C & Matricule : division, section, emplacement & O & caractère 4, 2, 4 & 1 \\
466+RL0105A & Code de l'utilisation prédominante (1000 à 9999) & O & caractère 4 & 1 \\
467+RL0107A & Numéro d'unité de voisinage & N & caractère 4 & 1 \\
468+RL0302A & Superficie du terrain porté au rôle & N & réel 15, 2 déc. & 3 \\
469+RL0307A / RL0307B & Année de construction originelle du bâtiment principal ; réelle (R) ou estimée (E) & N & entier 4 ; caractère 1 & 3 \\
470+RL0308A & Aire d'étages du bâtiment principal & N & réel 8, 1 déc. & 3 \\
471+RL0311A & Nombre total de logements & N & entier 4 & 3 \\
472+RL0401A & Date à laquelle les conditions du marché ont été considérées & O & date & 4 \\
473+RL0402A / RL0403A & Valeur du terrain ; valeur du ou des bâtiments & N & entier 10 & 4 \\
474+RL0404A & Valeur de l'immeuble inscrite au rôle en vigueur & O & entier 10 & 4 \\
475+RL0405A & Valeur du même immeuble au rôle antérieur & N & entier 10 & 4 \\
476+RL0501A & Appartenance à la catégorie des terrains vagues desservis & O & booléen & 5 \\
477+\bottomrule
478+\end{tabular}
479+\tabsource{MAMH, Répertoire des renseignements prescrits du rôle d'évaluation foncière en format ouvert, version 2.5 (24 mars 2022), fichier \texttt{data/raw/mamh\_roles\_evaluation/} \parencite{dqrepertoire} ; sélection et lecture de l'auteur.}
480+\end{table}
481+
482+\begin{figure}[tbp]
483+\centering
484+\includegraphics[width=\linewidth]{ch13/ch13-fig-rfu.pdf}
485+\caption{Composition de l'évaluation imposable uniformisée 2026 par catégorie d'immeubles pour Montréal, Québec et Gatineau, et montant total (en milliards de dollars). La part des résidences d'un logement passe de 20~\% à Montréal à 56~\% à Gatineau ; celle des immeubles à logements et des copropriétés suit le mouvement inverse.}\label{fig:13:rfu}
486+\figsource{Ministère des Affaires municipales et de l'Habitation, Richesse foncière uniformisée 2026 (mise à jour du 8 octobre 2026, licence CC BY 4.0), fichier \texttt{data/raw/mamh\_rfu\_2026/RFU-2026.csv}, consulté le 31 octobre 2026 ; script \texttt{analysis/python/figures/ch13\_rfu.py}.}
487+\end{figure}
488+
489+\section{Analyse de sensibilité : la convention de date et la définition de l'indicateur}\label{sec:13:sensibilite}
490+
491+Deux conventions de lecture des données, parmi celles exposées dans ce chapitre, ont un effet chiffrable sur une conclusion de valeur : la date attribuée à une vente et la définition de la variation d'un loyer. Le \cref{tab:13:biais} applique l'\cref{eq:13:biais} à des croissances annuelles de $-3$ à $10$~\% et à des délais de un à six mois entre la promesse et l'acte. Pour un marché qui progresse de 6~\% par année et un délai de trois mois, dater les comparables à l'acte sous-estime chaque prix ajusté de 1,47~\% ; à 10~\% et trois mois, de 2,41~\% ; à 10~\% et six mois, cas des ventes conditionnelles dans un marché tendu, de 4,88~\%. La \cref{fig:13:biais} prolonge le tableau à 15~\% par année, ordre de grandeur atteint par certains marchés québécois en 2021 et 2022. Ces biais sont du même ordre que la précision espérée d'une grille de comparaison et, comme la \cref{prop:datation} l'a montré, ils sont systématiques : ils affectent tous les comparables dans le même sens et ne se compensent pas. Dans un marché stable, la convention de date est sans conséquence ; dans un marché qui bouge, elle en a une, et le rapport doit dire laquelle il applique.
492+
493+\begin{table}[tbp]
494+\caption{Biais relatif (\%) sur le prix ajusté d'un comparable daté à l'acte plutôt qu'à la promesse d'achat, selon la croissance annuelle composée du marché et le délai promesse--acte (\cref{eq:13:biais})}\label{tab:13:biais}
495+\centering\small
496+\begin{tabular}{@{}S[table-format=-2.0] S[table-format=-1.2] S[table-format=-1.2] S[table-format=-1.2] S[table-format=-1.2]@{}}
497+\toprule
498+{Croissance $g$ (\%/an)} & {$\delta = 1$ mois} & {$\delta = 2$ mois} & {$\delta = 3$ mois} & {$\delta = 6$ mois} \\
499+\midrule
500+-3 & 0.25 & 0.51 & 0.76 & 1.51 \\
501+0 & 0.00 & 0.00 & 0.00 & 0.00 \\
502+3 & -0.25 & -0.49 & -0.74 & -1.49 \\
503+6 & -0.49 & -0.98 & -1.47 & -2.96 \\
504+10 & -0.80 & -1.60 & -2.41 & -4.88 \\
505+\bottomrule
506+\end{tabular}
507+\tabsource{calcul analytique, \texttt{analysis/python/cases/ch13\_sources.py}, fonction \texttt{biais\_datation} ; le signe est celui du biais sur le prix ajusté (négatif en marché haussier).}
508+\end{table}
509+
510+\begin{figure}[tbp]
511+\centering
512+\includegraphics[width=\linewidth]{ch13/ch13-fig-biais-datation.pdf}
513+\caption{Écart relatif entre le prix d'une vente datée à l'acte et le même prix daté à la promesse d'achat, en fonction du délai entre les deux et de la croissance annuelle du marché. La figure montre l'ampleur de la correction $(1+g)^{\delta/12} - 1$ à appliquer au prix daté à l'acte pour le ramener à la date de la promesse ; le biais du \cref{tab:13:biais} en est l'opposé.}\label{fig:13:biais}
514+\figsource{calcul analytique, script \texttt{analysis/python/figures/ch13\_biais\_datation.py} ; la plage de croissances est illustrative.}
515+\end{figure}
516+
517+La seconde sensibilité est celle de la définition de la variation d'un loyer, mesurée sur les données réelles du \cref{tab:13:schl}. Un évaluateur qui indexe le loyer de marché d'un logement de deux chambres à Montréal d'octobre 2024 à octobre 2025 avec la variation brute du loyer moyen l'augmente de 14,5~\% ; avec la variation estimative, de 7,2~\%. Sur un loyer de \dollars{1300}, l'écart est de 95~\$ par mois, soit \dollars{1140} par année et par logement ; sur un immeuble de douze logements capitalisé à 4,5~\%, il représente près de \dollars{300000} de valeur. L'effet de composition est aussi grand à Trois-Rivières et il change de signe à Sherbrooke. Il n'y a pas ici de « bonne » définition dans l'absolu : la variation estimative mesure l'évolution du loyer d'un logement donné et convient à l'indexation d'un loyer de marché ; la variation brute mesure l'évolution du loyer moyen du parc et convient à l'analyse des revenus agrégés. L'erreur est de prendre l'une pour l'autre, et elle est fréquente parce que les communiqués publient la première et que les tableaux donnent les niveaux dont on déduit la seconde.
518+
519+\begin{erreurs}
520+\begin{itemize}
521+\item \emph{Chercher chez Statistique Canada un prix de vente des propriétés.} L'organisme ne publie aucun tableau de prix de vente résidentiels au Québec ; les prix de transaction sont d'industrie (APCIQ, JLR, Teranet) et se citent comme tels, avec leur univers et leur convention de date.
522+\item \emph{Dater une vente à l'acte sans le dire.} Le registre, JLR et le rôle ne connaissent que la date de l'acte ; en marché haussier de 6~\% et avec trois mois de délai, l'erreur est de 1,5~\% sur chaque comparable, toujours dans le même sens.
523+\item \emph{Prendre le prix d'acte pour le prix de l'immeuble.} Les meubles, les contreparties non monétaires et les financements avantageux sont dans le montant déclaré ; le ratio prix/rôle uniformisé de l'\cref{eq:13:ratio} signale les cas à vérifier.
524+\item \emph{Traiter la valeur au rôle comme une valeur à la date d'évaluation.} Elle est établie aux conditions du marché du 1\textsuperscript{er} juillet du deuxième exercice précédant le rôle (art.~46 LFM), selon les conventions de mesure du Manuel, et ses composantes terrain et bâtiment sont facultatives dans le fichier ouvert.
525+\item \emph{Indexer un loyer de marché avec la variation brute du loyer moyen de la SCHL.} La variation estimative, fondée sur les immeubles présents dans l'échantillon les deux années, est la mesure à logement constant ; l'écart dépassait sept points à Montréal en 2025.
526+\item \emph{Indexer un coût de construction avec l'IPLN ou l'IPC.} Entre 2017 et 2026, l'IPCB résidentiel de Montréal a progressé de 80~\%, l'IPLN de 59~\% et l'IPC de 32~\% ; seul le premier mesure un coût de construction.
527+\item \emph{Utiliser le tableau 18-10-0276-01 ou une série nationale.} Le premier est archivé depuis 2024 ; la seconde ne progressait que de 2,7~\% au troisième trimestre de 2026 quand les deux RMR québécoises progressaient de 8,6 et 9,2~\%.
528+\item \emph{Reproduire un tableau protégé.} Les barèmes du Manuel, les tableaux d'Altus, de Marshall \& Swift, de RSMeans et de l'APCIQ se citent par valeurs ponctuelles avec attribution ; ils ne se copient pas.
529+\item \emph{Réidentifier le rôle ouvert.} La licence CC BY du jeu l'interdit expressément ; les croisements qui permettraient de retrouver un propriétaire sont exclus.
530+\end{itemize}
531+\end{erreurs}
532+
533+\begin{limites}
534+\begin{itemize}
535+\item L'inventaire des \cref{tab:13:inventaire,tab:13:acces} reflète l'état des sources au 31 octobre 2026 ; les adresses, les licences, les tarifs et les tableaux changent, et le tableau 18-10-0276-01 en est l'exemple. Chaque usage exige une nouvelle vérification datée.
536+\item La grille tarifaire du registre foncier citée est celle en vigueur à compter du 1\textsuperscript{er} avril 2025, publiée sous l'intitulé « janvier 2026 » ; l'existence d'une indexation postérieure n'a pas pu être vérifiée.
537+\item Les modalités et le prix des abonnements aux données détaillées de Centris et de JLR, ainsi que les conditions d'utilisation des séries téléchargeables de l'indice Teranet--Banque Nationale, n'ont pas été vérifiés ; les conditions de la SCHL ont été lues sur sa page générale, non dans un contrat de licence.
538+\item Le jeu Données Québec annonce 1~140 fichiers XML ; l'index 2026 déposé dans le dépôt en compte 1~134 et le fichier de richesse foncière 1~132 municipalités locales. L'écart n'a pas été élucidé (territoires non organisés, regroupements) et aucun fichier XML de rôle n'a été téléchargé ni lu ; la description des champs repose sur le répertoire et non sur les fichiers.
539+\item Le dénombrement des codes du répertoire (62) résulte d'une extraction automatique du texte d'un PDF ; une lecture visuelle donne un ou deux codes de plus dans la section 1. Les équations du dictionnaire de la richesse foncière n'ont pas toutes été vérifiées, seule l'identité entre la somme des six postes par catégorie et l'évaluation imposable l'a été.
540+\item La méthodologie d'échantillonnage de l'Enquête sur les logements locatifs (plan de sondage, taux de réponse, seuils des lettres de qualité) n'est décrite ici que dans la mesure où les tableaux publiés la documentent ; les seuils numériques des lettres a à d ne figurent pas dans les classeurs et ne sont pas cités.
541+\item Les règles sur la protection des renseignements personnels sont mentionnées de façon générale, sans référence à une loi précise, parce que leur texte n'a pas été vérifié pour cet ouvrage ; un juriste doit être consulté avant toute constitution de fichier nominatif.
542+\item Les comparaisons entre APCIQ, JLR et Teranet portent sur des périodes et des univers différents par construction ; elles illustrent la nécessité de nommer la source, non une mesure de l'écart entre marchés.
543+\end{itemize}
544+\end{limites}
545+
546+\begin{synthese}
547+\begin{itemize}
548+\item Les données de l'évaluateur viennent de trois familles : registres administratifs (registre foncier, rôle, permis), statistiques officielles (Statistique Canada, SCHL, Banque du Canada, TAL) et données d'industrie (APCIQ, JLR, Teranet, CBRE, guides de coûts). La distinction officielle/industrie détermine ce qu'on peut savoir de la méthode, reproduire, citer et redistribuer.
549+\item Quatre régimes de réutilisation coexistent : licence ouverte (Statistique Canada, CC BY du MAMH), conditions d'utilisation d'organismes publics (SCHL, Banque du Canada), documents publics protégés (Manuel, règlements) et données d'industrie sous droit d'auteur. On cite des valeurs ponctuelles avec source et date ; on ne reproduit pas de tableau protégé ; on ne réidentifie pas le rôle ouvert.
550+\item Une vente laisse trois traces : l'acte (prix, parties, garantie, meubles), la fiche de courtage (date de la promesse, mise en marché, déclarations du vendeur) et le rôle (caractéristiques, valeur datée). Le prix d'acte vaut $P^{\ast} + M + F + \eta$ ; la date pertinente est celle de la promesse, et le biais de datation $(1+g)^{-\delta/12} - 1$ est systématique.
551+\item Les ventes liées, successorales, sous contrainte, de promoteur, multiples ou nominales se signalent par des champs identifiables (exonérations LDMI, vendeur institutionnel, absence de garantie, prix sous 5~000~\$) ; le journal des exclusions conserve chaque décision.
552+\item La SCHL mesure les loyers payés et l'inoccupation des immeubles de trois logements et plus, avec des lettres de qualité et une variation estimative à logement constant distincte de la variation brute ; le TAL fournit les règles qui administrent la trajectoire des loyers contractuels, non des loyers observés.
553+\item L'IPCB (18-10-0289-01) mesure un coût de construction ; l'IPLN un prix de logements neufs ; l'IPC un panier de consommation. Les séries résidentielles de Montréal commencent en 2017, celles de Québec en 2023 ; le tableau 18-10-0276-01 est archivé.
554+\item Une base d'évaluation est une chaîne documentée : acquisition datée, normalisation, appariement par matricule, lot et adresse, épuration journalisée, enrichissement et dictionnaire ; sa reproductibilité est ce qui permet au rapport de décrire l'étendue des recherches.
555+\end{itemize}
556+\end{synthese}
557+
558+\section*{Exercices}
559+\addcontentsline{toc}{section}{Exercices}
560+
561+\begin{exercice}[1]{Classer les sources et budgéter une collecte}{ex:13:1}
562+Pour chacune des sources suivantes, indiquez si la donnée est officielle ou d'industrie, sous quel régime de réutilisation elle est diffusée et ce qu'un rapport d'évaluation peut en reproduire : (a) les prix médians trimestriels de l'APCIQ ; (b) le tableau 18-10-0289-01 de Statistique Canada ; (c) le loyer moyen d'une zone de la SCHL ; (d) le rôle d'évaluation en format ouvert ; (e) les barèmes de la partie 3E du Manuel d'évaluation foncière ; (f) la fourchette de coûts du \emph{Canadian Cost Guide}. Puis, avec la grille tarifaire du registre foncier en vigueur à compter du 1\textsuperscript{er} avril 2025 (consultation à 1~\$ par document, état certifié à 16~\$ plus 16~\$ par réquisition), calculez le coût de la lecture de l'index des immeubles et de l'acte de vingt-cinq comparables candidats et de trois états certifiés à une réquisition chacun.
563+\end{exercice}
564+
565+\begin{exercice}[1]{Du prix d'acte au prix du comparable}{ex:13:2}
566+Un acte de vente publié le 15 septembre 2026 déclare un prix de \dollars{612000} « incluant les meubles » ; la fiche de courtage indique une promesse d'achat acceptée le 2 juillet 2026 et une liste d'inclusions dont la contrepartie documentée est de \dollars{12000}. Le marché du secteur progresse de 6~\% par année (taux composé). Calculez le prix à retenir pour l'immeuble, le délai entre la promesse et l'acte en mois (une année de 365,25 jours), le facteur de correction de datation $(1+g)^{\delta/12}$ et l'écart en dollars qu'introduirait, sur le prix transporté à la date d'évaluation, le fait de dater la vente à l'acte. Quel signe a ce biais et pourquoi ne se compense-t-il pas entre comparables ?
567+\end{exercice}
568+
569+\begin{exercice}[2]{Signatures des ventes non représentatives}{ex:13:3}
570+Un extrait de registre foncier livre les transactions suivantes (fictives) pour un secteur. Pour chacune, dites quels champs du \cref{tab:13:pieges} la signalent, si elle est à conserver, à ajuster ou à exclure, et ce qu'il faudrait vérifier dans une autre source : (a) vente de \dollars{1} entre deux personnes portant le même nom de famille, avec déclaration d'exonération du droit de mutation ; (b) vente par une institution financière, sans garantie de qualité, précédée à l'index d'un préavis d'exercice d'un droit hypothécaire ; (c) vente par « Succession X » à un acheteur sans lien, après une inscription de 52 jours chez un courtier ; (d) vente d'un lot nouvellement créé par une société de construction, prix incluant les taxes, promesse signée seize mois avant l'acte ; (e) vente d'une moitié indivise à l'autre copropriétaire ; (f) vente avec balance de prix de vente de \dollars{200000} à 2~\% sur cinq ans ; (g) vente de \dollars{4800} d'une parcelle résiduelle ; (h) vente ordinaire par courtier dont le ratio prix/rôle uniformisé est de 0,55.
571+\end{exercice}
572+
573+\begin{exercice}[2]{Lire l'Enquête sur les logements locatifs}{ex:13:4}
574+À partir du \cref{tab:13:schl} : (a) calculez la variation brute du loyer moyen des deux chambres entre octobre 2024 et octobre 2025 pour les six RMR et comparez-la à la variation estimative publiée ; (b) calculez le nombre de logements vacants implicite dans chaque RMR en octobre 2025 ; (c) pour un logement de deux chambres loué \dollars{1300} à Montréal en octobre 2024, calculez le loyer indexé en octobre 2025 selon chacune des deux variations et l'écart annuel ; (d) expliquez dans quel cas chaque variation est la bonne et ce que signifie la lettre de qualité « c » attachée au taux d'inoccupation de Gatineau.
575+\end{exercice}
576+
577+\begin{exercice}[3]{Lire un tableau de Statistique Canada par script}{ex:13:5}
578+À l'aide du script \texttt{analysis/python/cases/ch13\_sources.py} et du fichier \texttt{data/raw/statcan\_18100289/18100289.csv}, extrayez les séries « Bâtiments résidentiels », agrégat des divisions, pour Montréal, pour la RMR de Québec et pour le composite des quinze RMR. (a) Indiquez la première période disponible et le nombre d'observations de chaque série. (b) Calculez le facteur d'indexation du premier trimestre de 2023 au troisième trimestre de 2026 pour les trois séries et le taux de croissance annuel composé correspondant. (c) Pour Montréal et le composite, calculez aussi le facteur du troisième trimestre de 2019 au troisième trimestre de 2026, et expliquez pourquoi ce calcul est impossible pour Québec. (d) Un coût de construction de \dollars{350000} établi à Québec au premier trimestre de 2023 doit être exprimé en dollars du troisième trimestre de 2026 : donnez le résultat avec la série de Québec, puis l'erreur commise si l'on utilise la série de Montréal ou le composite.
579+\end{exercice}
580+
581+\begin{exercice}[3]{Concevoir une base et mesurer sa couverture}{ex:13:6}
582+Sur un marché et une période donnés, un extrait du registre foncier compte, après exclusion des ventes non représentatives, \num{1500} ventes sincères (chiffres fictifs) ; la base de courtage en compte \num{1044}, dont \num{920} ont pu être appariées à un acte par l'adresse et la date. Le prix moyen des ventes par courtier est de \dollars{510000} et celui des autres ventes, connu du registre, de \dollars{455000}. (a) Calculez le taux de couverture, le taux d'appariement et le prix moyen du marché par l'\cref{eq:13:composition}, puis le biais absolu et relatif d'une statistique fondée sur le seul courtage. (b) Proposez, pour cette base, le schéma des tables et les clés d'appariement en vous appuyant sur le \cref{tab:13:schema} et sur les codes du \cref{tab:13:dico}, et dites quelle clé relie chaque paire de sources. (c) Rédigez (200 mots) le contenu du fichier de sources et du journal des exclusions que le rapport devrait pouvoir produire, en précisant pour chaque source la licence et ce qui peut être reproduit.
583+\end{exercice}
584+
585+\section*{Pour aller plus loin}
586+\addcontentsline{toc}{section}{Pour aller plus loin}
587+
588+Le manuel international sur les indices de prix des logements coordonné par Eurostat \parencite{eurostat2013} consacre des chapitres entiers aux sources de données de prix (registres, prêteurs, courtage, annonces, évaluations) et à leurs biais de couverture et de datation ; c'est la référence méthodologique la plus complète sur les questions de la \cref{sec:13:transactions}. \textcite{krauseLipscomb2016} proposent un protocole de préparation des données immobilières, de l'acquisition à la documentation, qui a inspiré la chaîne de la \cref{sec:13:base} ; \textcite{gloudemans1999} et le Standard on Mass Appraisal de l'IAAO \parencite{iaaomass2025} décrivent les exigences de collecte et de validation des ventes de l'évaluation de masse, dont le Manuel d'évaluation foncière du Québec \parencite{mefq2026} est la transposition québécoise. Sur la construction d'indices à partir d'actes et sur leurs révisions, \textcite{caseShiller1987} et \textcite{silverstein2014} restent les lectures de base, et \textcite{lentzWang1998} passent en revue la qualité des données d'évaluation dans la chaîne hypothécaire. La recension de \textcite{kholodilin2024} sur les effets du contrôle des loyers éclaire ce que mesurent, et ne mesurent pas, les loyers observés dans un parc encadré comme celui du Québec. Enfin, les sources primaires de ce chapitre sont les sources elles-mêmes : le Répertoire des renseignements prescrits du rôle \parencite{dqrepertoire}, les tableaux de la SCHL \parencite{schlrmltables2025}, les métadonnées de Statistique Canada \parencite{statcanIPCB} et la méthodologie publiée dans les rapports de JLR \parencite{jlr202609} et de Teranet--Banque Nationale \parencite{teranet202609} ; le lecteur qui veut aller plus loin commencera par les lire dans leur fichier brut, comme l'exemple de la \cref{sec:13:exemple} l'a fait.
modified manuscript/chapters/ch19.tex +1 −1
@@ -537,7 +537,7 @@ GWR sans secteur (coefficients locaux) & 0,086 & --- & $-0{,}002$ & 627 519 & $-
537 537
538 \section{Sensibilité au choix de la matrice de pondération}\label{sec:19:sensibilite} 538 \section{Sensibilité au choix de la matrice de pondération}\label{sec:19:sensibilite}
539 539
540−Le \cref{tab:19:sensibilite} et la \cref{fig:19:sensibilite} reprennent les estimations de la spécification sans secteur pour cinq matrices de plus proches voisins et trois matrices de distance inverse. La statistique de Moran des résidus est remarquablement stable, entre 0,32 et 0,33 quelle que soit la matrice : la dépendance est un fait des données, pas de la matrice. Les paramètres autorégressifs, eux, ne le sont pas du tout. Le $\rho$ du SAR passe de 0,24 avec quatre voisins à 0,65 avec seize, et jusqu'à 0,84 avec la distance inverse à quatre kilomètres (141 voisins en moyenne) ; le $\lambda$ du SEM, de 0,51 à 0,79, puis 0,89. Plus le voisinage est large, plus le décalage $\mathbf{W}\mathbf{y}$ est lissé, et plus il faut un coefficient élevé pour reproduire la même covariance résiduelle ; le paramètre et la matrice ne sont identifiés qu'ensemble. L'AIC, de son côté, s'améliore avec l'étendue du voisinage, parce qu'un voisinage de plusieurs kilomètres approche mieux la structure par secteurs du générateur qu'un voisinage de quatre maisons. Deux conséquences pratiques en découlent. D'abord, un paramètre autorégressif ne se compare pas d'une étude à l'autre sans la matrice qui l'accompagne, et il ne mesure pas une « intensité de l'effet de voisinage » qui existerait indépendamment d'elle. Ensuite, les grandeurs utiles à l'évaluateur, la prédiction pour un sujet et la correction de localisation, sont bien moins sensibles au choix de la matrice que les paramètres eux-mêmes, parce que les différentes matrices mobilisent à peu près la même information locale ; l'\cref{ex:19:7} le vérifie. 540+Le \cref{tab:19:sensibilite} et la \cref{fig:19:sensibilite} reprennent les estimations de la spécification sans secteur pour cinq matrices de plus proches voisins et trois matrices de distance inverse. La statistique de Moran des résidus est remarquablement stable, entre 0,32 et 0,33 quelle que soit la matrice : la dépendance est un fait des données, pas de la matrice. Les paramètres autorégressifs, eux, ne le sont pas du tout. Le $\rho$ du SAR passe de 0,24 avec quatre voisins à 0,65 avec seize, et jusqu'à 0,84 avec la distance inverse à quatre kilomètres (141 voisins en moyenne) ; le $\lambda$ du SEM, de 0,51 à 0,79, puis 0,89. Plus le voisinage est large, plus le décalage $\mathbf{W}\mathbf{y}$ est lissé, et plus il faut un coefficient élevé pour reproduire la même covariance résiduelle ; le paramètre et la matrice ne sont identifiés qu'ensemble. L'AIC, de son côté, s'améliore avec l'étendue du voisinage, parce qu'un voisinage de plusieurs kilomètres approche mieux la structure par secteurs du générateur qu'un voisinage de quatre maisons. Deux conséquences pratiques en découlent. D'abord, un paramètre autorégressif ne se compare pas d'une étude à l'autre sans la matrice qui l'accompagne, et il ne mesure pas une « intensité de l'effet de voisinage » qui existerait indépendamment d'elle. Ensuite, les grandeurs utiles à l'évaluateur, la prédiction pour un sujet et la correction de localisation, sont moins sensibles au choix de la matrice que les paramètres eux-mêmes, parce que les différentes matrices mobilisent à peu près la même information locale, mais elles ne sont pas insensibles : l'\cref{ex:19:7} montre que l'indication du SEM pour le sujet varie de 4 \% entre un voisinage de quatre ventes et un voisinage de seize, ce qui est du même ordre que l'écart entre le modèle sans secteur et le modèle de référence. Le voisinage retenu fait partie des choix à documenter et à soumettre à l'analyse de sensibilité.
541 541
542 \begin{table}[htbp] 542 \begin{table}[htbp]
543 \caption{Sensibilité au choix de la matrice de pondération, spécification sans indicatrices de secteur (données synthétiques)}\label{tab:19:sensibilite} 543 \caption{Sensibilité au choix de la matrice de pondération, spécification sans indicatrices de secteur (données synthétiques)}\label{tab:19:sensibilite}
added references/lib-ch19.bib +11 −0
@@ -0,0 +1,11 @@
1+% Chapitre 19 — Modèles spatiaux et temporels : aucune nouvelle référence.
2+% Toutes les clés citées dans manuscript/chapters/ch19.tex existent déjà dans references/library.bib
3+% (anselin1988, lesagePace2009, elhorst2014, dubin1988, dubin1998, paceBarrySirmans1998, paceBarryClappRodriguez1998,
4+% basuThibodeau1998, gillenThibodeauWachter2001, kelejianPrucha1998, kelejianPrucha2010, dubeLegros2013, dubeLegros2014,
5+% dubeLegrosTheriaultDesrosiers2014, dubeLegrosTheriaultDesrosiers2017, fotheringhamBrunsdonCharlton2002,
6+% bourassaCantoniHoesli2010, gelfandGhoshKnightSirmans1998, banerjeeGelfandKnightSirmans2004, desrosiersDubeTheriault2011,
7+% desrosiersTheriaultVilleneuve2000, theriaultDesrosiersVilleneuveKestens2003, kestensTheriaultDesrosiers2006,
8+% theriaultDesrosiers2011, goodmanThibodeau2003, bourassaHoesliPeng2003, bishop2020, kuminoffParmeterPope2010,
9+% apciqgatineaut32026, dqroles, gatineaureglement994, oeaqNormes, iaaomass2025, iaaoavm2018).
10+% Référence souhaitée mais NON ajoutée faute de vérification : Anselin, Bera, Florax & Yoon (1996), Regional Science and
11+% Urban Economics 26(1), 77-104 (tests LM robustes) — consignée dans verification/chapters/ch19-claims.csv (CLM-19-06).
modified solutions/ch19.tex +40 −0
@@ -1 +1,41 @@
1 % Solutions du chapitre 19 — Modèles spatiaux et temporels 1 % Solutions du chapitre 19 — Modèles spatiaux et temporels
2+% Les chiffres sont vérifiés par analysis/python/tests/test_ch19.py (test_solutions_exercices, test_solution_exercice_7).
3+
4+\begin{solution}{ex:19:1}{Statistique de Moran à la main}
5+La matrice de contiguïté binaire relie chaque maison à ses adjacentes ; après standardisation en ligne, les maisons 1 et 5 donnent un poids de 1 à leur unique voisine et les maisons 2, 3 et 4 un poids de $\tfrac12$ à chacune des deux leurs. La moyenne des résidus est nulle, donc $\mathbf{z} = \mathbf{e}$. Les décalages spatiaux $(\mathbf{W}\mathbf{z})_i$ sont $0{,}06$ ; $0{,}04$ ; $-0{,}01$ ; $-0{,}04$ ; $-0{,}08$ (par exemple, pour la maison 2 : $\tfrac12(0{,}10 - 0{,}02) = 0{,}04$). Les produits $z_i (\mathbf{W}\mathbf{z})_i$ valent $0{,}0060$ ; $0{,}0024$ ; $0{,}0002$ ; $0{,}0032$ ; $0{,}0048$, de somme $\mathbf{z}\T\mathbf{W}\mathbf{z} = 0{,}0166$, et $\mathbf{z}\T\mathbf{z} = 0{,}0240$. Avec $n = 5$ et $S_0 = 5$,
6+\[
7+I = \frac{5}{5}\cdot\frac{0{,}0166}{0{,}0240} = 0{,}692 .
8+\]
9+L'espérance sous l'hypothèse nulle est $\E[I] = -1/(n-1) = -0{,}25$ ; la variance de l'\cref{eq:19:moran-moments} vaut $0{,}158$, d'où $z_I = (0{,}692 + 0{,}25)/0{,}398 = 2{,}37$ (valeur $p$ bilatérale de 0,018). Le signe positif traduit un regroupement : les résidus positifs sont voisins de résidus positifs, les négatifs de négatifs, ce que l'on voit directement sur la rue où les résidus décroissent d'un bout à l'autre. Si les résidus alternaient, par exemple $0{,}10$ ; $-0{,}10$ ; $0{,}10$ ; $-0{,}10$ ; $0{,}10$, chaque décalage aurait le signe opposé au résidu et l'on trouverait $I = -1$ exactement sur cette configuration : c'est l'autocorrélation négative, celle d'un damier.
10+\end{solution}
11+
12+\begin{solution}{ex:19:2}{Dépendance ou hétérogénéité ?}
13+(a) Dépendance spatiale : un attribut de voisinage omis, le parc, affecte le résidu de toutes les maisons alentour. Remède : ajouter une variable d'accessibilité au parc ; à défaut, un modèle à erreur spatiale ou le krigeage des résidus corrigent la prédiction locale. (b) Hétérogénéité spatiale : le prix implicite du garage varie avec le lieu. Remède : une interaction garage $\times$ quartier ou une régression géographiquement pondérée, dont la carte du coefficient du garage montrera la variation. (c) Dépendance par interaction entre les prix : les acheteurs s'ancrent sur les ventes récentes du même développement. Remède : un modèle à décalage spatio-temporel avec une matrice de ventes antérieures, en vérifiant par les tests LM robustes que la dépendance n'est pas, en réalité, un résidu de qualité commun au développement. (d) Hétérogénéité : les coefficients diffèrent entre sous-marchés. Remède : un test de Chow entre arrondissements (\cref{ch:15}), puis des modèles séparés ou une GWR ; si l'écart tient à des variables omises corrélées avec la superficie dans un arrondissement, c'est un problème de spécification plutôt que d'hétérogénéité. (e) Dépendance des ratios d'évaluation : le quartier est sous-évalué au rôle, ce qui signale soit un effet fixe de quartier manquant dans le modèle d'évaluation de masse, soit une variation de marché depuis la date de référence qui lui est propre. Remède : statistique de Moran des ratios par unité de voisinage, puis révision des effets fixes ou des facteurs de localisation (\cref{ch:9}).
14+\end{solution}
15+
16+\begin{solution}{ex:19:3}{Lire les tests LM}
17+Le seuil critique d'une loi du $\chi^2$ à un degré de liberté est $3{,}841$ au niveau de 5 \%. Les deux tests simples le dépassent largement ($48{,}2$ et $31{,}5$) : il y a dépendance spatiale, mais chacun des tests simples est aussi sensible à l'autre forme, et l'on passe aux versions robustes. La statistique robuste de l'erreur, $17{,}9$, reste très significative ; celle du décalage, $1{,}2$, ne l'est pas, avec une valeur $p$ d'environ 0,27 (à titre de repère, une statistique de $1{,}09$ a une valeur $p$ de 0,296). La règle séquentielle retient donc le modèle à erreur spatiale : la dépendance réside dans les résidus, ce qui s'interprète comme des attributs de voisinage omis partagés par les maisons proches, écoles, nuisances, qualité du bâti environnant, ou comme une segmentation en sous-marchés que la spécification ignore. Le remède premier est d'enrichir la spécification en variables de localisation ou en effets fixes de quartier ; le SEM corrige l'inférence et la prédiction si l'on ne peut pas. Si les deux statistiques robustes avaient dépassé 20, les deux formes seraient présentes ou, plus vraisemblablement, la spécification serait déficiente (forme fonctionnelle, variables omises corrélées avec les régresseurs) ; on envisagerait un modèle de Durbin spatial, qui emboîte les deux formes, et surtout l'on reverrait les variables avant de conclure à une interaction entre les prix.
18+\end{solution}
19+
20+\begin{solution}{ex:19:4}{Effets directs, indirects et totaux}
21+Par la \cref{prop:effet-total}, l'effet total moyen vaut $\beta_r/(1 - \hat\rho)$ : pour la superficie, $0{,}5571/0{,}5615 = 0{,}992$ ; pour la qualité, $0{,}0604/0{,}5615 = 0{,}108$. Le multiplicateur spatial est $1/(1 - 0{,}4385) = 1{,}78$. L'effet indirect de la superficie est la différence entre le total et le direct, $0{,}992 - 0{,}5715 = 0{,}421$. Si l'élasticité vraie est 0,55, l'effet total la surestime de $0{,}992/0{,}55 - 1 = 80$ \% : les quatre dixièmes d'élasticité attribués aux « retombées » sur les voisines sont un artefact du paramètre $\rho$, qui ne mesure dans ce jeu de données que la prime de secteur omise. Le seul effet qu'un évaluateur pourrait utiliser pour dériver un ajustement de superficie est l'effet direct, $0{,}5715$, qui reste proche de la vérité parce que la rétroaction d'une maison sur elle-même à travers ses voisines est faible ; encore vaudrait-il mieux utiliser le coefficient d'un SEM ($0{,}5416$ dans l'exemple du chapitre) ou du modèle avec indicatrices ($0{,}5391$), qui ne reposent pas sur l'hypothèse d'une interaction entre les prix.
22+\end{solution}
23+
24+\begin{solution}{ex:19:5}{Poids d'un noyau gaussien}
25+Avec $k_j = \exp[-\tfrac12 (d_j/h)^2]$ et $h = 2$ km, les poids bruts des ventes à 0,5 ; 1 ; 2 et 5 km sont $0{,}969$ ; $0{,}882$ ; $0{,}607$ et $0{,}044$, soit, normalisés à somme unitaire, $0{,}387$ ; $0{,}353$ ; $0{,}242$ et $0{,}018$. Avec $h = 5$ km, les poids bruts sont $0{,}995$ ; $0{,}980$ ; $0{,}923$ et $0{,}607$, normalisés $0{,}284$ ; $0{,}280$ ; $0{,}263$ et $0{,}173$. La vente à 5 km est pratiquement exclue par la bande courte (moins de 2 \% du poids) et compte presque autant que les autres avec la bande longue (17 \%). La bande courte produit une régression locale fondée sur trois ventes effectives : elle suit fidèlement le marché immédiat (faible biais) mais ses coefficients sont très instables (forte variance), comme une grille de trois comparables ; la bande longue mobilise les quatre ventes, réduit la variance, mais importe l'information d'une vente possiblement située dans un autre sous-marché (biais). C'est l'arbitrage de l'\cref{eq:16:kopt} du \cref{ch:16} transposé à une pondération continue, et la validation croisée joue le rôle que le jugement de l'évaluateur joue dans le choix du nombre de comparables.
26+\end{solution}
27+
28+\begin{solution}{ex:19:6}{Krigeage ordinaire à trois voisins}
29+Les distances au sujet sont 1, 2 et 3 km ; entre les points, $d_{12} = \sqrt5 = 2{,}236$, $d_{13} = 4$ et $d_{23} = \sqrt{13} = 3{,}606$ km. Avec $C(h) = 0{,}009\,e^{-h/5}$ pour $h > 0$ et $C(0) = c_0 + c_1 = 0{,}0135$, la matrice des covariances entre points observés et le vecteur des covariances avec le sujet sont
30+\[
31+\mathbf{C} = \begin{bmatrix} 0{,}01350 & 0{,}00575 & 0{,}00404 \\ 0{,}00575 & 0{,}01350 & 0{,}00438 \\ 0{,}00404 & 0{,}00438 & 0{,}01350 \end{bmatrix}, \qquad
32+\mathbf{c}_0 = \begin{bmatrix} 0{,}00737 \\ 0{,}00603 \\ 0{,}00494 \end{bmatrix}.
33+\]
34+Le système de l'\cref{eq:19:krigeage}, à quatre équations avec la contrainte $w_1 + w_2 + w_3 = 1$, a pour solution $w_1 = 0{,}466$, $w_2 = 0{,}283$, $w_3 = 0{,}251$ et $\mu = -0{,}0016$. Le résidu krigé au sujet vaut $0{,}466 \times 0{,}08 + 0{,}283 \times 0{,}05 + 0{,}251 \times (-0{,}01) = 0{,}0489$, et la variance de krigeage $C(0) - \mathbf{w}\T\mathbf{c}_0 - \mu = 0{,}0135 - 0{,}00650 + 0{,}0016 = 0{,}00869$, soit un écart-type de 0,093. La moyenne simple des trois résidus est $0{,}040$ et la moyenne pondérée par l'inverse de la distance (poids $0{,}545$ ; $0{,}273$ ; $0{,}182$) est $0{,}0555$. Le krigeage se situe entre les deux et donne au point le plus éloigné un poids de 0,251 contre 0,182 pour l'inverse de la distance, et au plus proche 0,466 contre 0,545. Deux raisons : la pépite, qui représente un tiers de la variance, signifie que chaque résidu observé est bruité, de sorte qu'aucun point ne mérite un poids dominant et que les poids sont aplatis vers la moyenne ; et les points 1 et 2 sont relativement proches l'un de l'autre (covariance 0,00575), donc partiellement redondants, tandis que le point 3, isolé, apporte une information indépendante que le krigeage rémunère. L'inverse de la distance ignore à la fois le bruit et la redondance.
35+\end{solution}
36+
37+\begin{solution}{ex:19:7}{Sensibilité à la matrice et prédiction pour le sujet}
38+La fonction \texttt{exercice\_7} de \texttt{analysis/python/cases/ch19\_spatial.py} reproduit les calculs. Le SEM sans indicatrices de secteur donne $\hat\lambda = 0{,}507$ avec quatre voisins, $0{,}649$ avec huit et $0{,}786$ avec seize, soit une variation de 55 \% du paramètre. L'espérance conditionnelle du résidu au sujet vaut respectivement $-0{,}019$, $-0{,}052$ et $-0{,}055$, et les indications \dollars{656724}, \dollars{633352} et \dollars{628672}. L'écart entre les indications extrêmes est de 4,4 \%, bien moindre que la variation de $\hat\lambda$ mais du même ordre que l'écart de 4,1 \% entre le modèle sans secteur et le modèle de référence (\dollars{637953}) : la correction de localisation dépend de l'étendue du voisinage, parce qu'un voisinage de quatre ventes mesure le résidu local avec beaucoup de bruit et que le SEM le rétrécit alors davantage vers zéro. Avec une fenêtre de vingt-quatre mois au lieu de douze, la matrice spatio-temporelle ne laisse plus que 16 ventes sans voisine antérieure (contre 18), le voisinage moyen passe de 15,5 à 25,7 ventes et $\hat\rho_{\text{st}}$ monte de $0{,}370$ à $0{,}415$ (erreur-type 0,062) : élargir la fenêtre lisse le décalage et, comme pour les matrices spatiales, accroît le paramètre sans que la dépendance sous-jacente ait changé.
39+
40+\emph{Note (environ 200 mots).} Un ajustement de localisation dérivé d'un modèle spatial n'est pas un chiffre unique mais le résultat d'une chaîne de choix : spécification des caractéristiques, définition du voisinage (nombre de voisins ou portée, fenêtre temporelle), forme du modèle (erreur, décalage, krigeage, coefficients locaux) et méthode de retransformation. Le dossier doit déclarer chacun de ces choix et montrer que la conclusion y résiste. Les résultats de cet exercice indiquent que le paramètre autorégressif lui-même ne doit jamais être présenté comme une mesure de l'« effet de voisinage », puisqu'il change du simple au double selon la matrice ; ce qui doit être rapporté, c'est la correction de localisation au sujet, avec sa fourchette selon des voisinages raisonnables (ici, de $-1{,}9$ \% à $-5{,}4$ \% en logarithme) et la précision du modèle en validation croisée. Lorsque la fourchette est large, comme ici entre quatre et huit voisins, c'est le signe que le voisinage immédiat du sujet est hétérogène ou mince, et l'évaluateur doit le dire, revenir aux ventes elles-mêmes et vérifier ce qui, dans les quatre plus proches, s'écarte de la tendance du secteur. L'analyse de sensibilité fait partie de la preuve, au même titre que le modèle retenu, et la fourchette qu'elle produit alimente la réconciliation du \cref{ch:20}.
41+\end{solution}
modified verification/calculations.csv +14 −0
@@ -275,3 +275,17 @@ CALC-12-009,12,"Ajustement de superficie du comparable G0404 (506 500 $, 160 m²
275 CALC-12-010,12,"Variance combinée β√(σs² + σc²/n) : 2 %/2 %/5 → 1,181 % = 7 535 $ (part du sujet 83,3 %) ; sujet seul 6 878 $ ; comparables seuls 3 076 $ ; 5 %/2 % → 17 468 $",analysis/python/cases/ch12_mesurage.py,analysis/python/tests/test_ch12.py::TestPropagation::test_variance_combinee,7 535 ; 6 878 ; 3 076 ; 17 468,7 535 ; 6 878 ; 3 076 ; 17 468,verifie,2026-10-31 275 CALC-12-010,12,"Variance combinée β√(σs² + σc²/n) : 2 %/2 %/5 → 1,181 % = 7 535 $ (part du sujet 83,3 %) ; sujet seul 6 878 $ ; comparables seuls 3 076 $ ; 5 %/2 % → 17 468 $",analysis/python/cases/ch12_mesurage.py,analysis/python/tests/test_ch12.py::TestPropagation::test_variance_combinee,7 535 ; 6 878 ; 3 076 ; 17 468,7 535 ; 6 878 ; 3 076 ; 17 468,verifie,2026-10-31
276 CALC-12-011,12,"Atténuation : Var(ẽ) = 0,0609 ; sd(ln S) = 0,2487 ; λ(0,10) = 0,859, β̂ moyen 0,465 ; λ(0,20) = 0,604, β̂ 0,328 ; 200 réplications, graine 20261031",analysis/python/cases/ch12_mesurage.py,analysis/python/tests/test_ch12.py::TestAttenuation::test_theorie_et_simulation,"0,0609 ; 0,859/0,465 ; 0,604/0,328","0,06092 ; 0,8590/0,4651 ; 0,6037/0,3280",verifie,2026-10-31 276 CALC-12-011,12,"Atténuation : Var(ẽ) = 0,0609 ; sd(ln S) = 0,2487 ; λ(0,10) = 0,859, β̂ moyen 0,465 ; λ(0,20) = 0,604, β̂ 0,328 ; 200 réplications, graine 20261031",analysis/python/cases/ch12_mesurage.py,analysis/python/tests/test_ch12.py::TestAttenuation::test_theorie_et_simulation,"0,0609 ; 0,859/0,465 ; 0,604/0,328","0,06092 ; 0,8590/0,4651 ; 0,6037/0,3280",verifie,2026-10-31
277 CALC-12-012,12,"Exercices 1, 3, 4, 5, 6 : 1 176 pi² = 109,25 m², 42,67 m ; 87,36 m², 171,52 m², 1,154 ; 2 042,86 $/m², 8 580 $, +8 523/−8 639 $, 15 600 $ ; +7,91 %, 10 447 $, 55 572 $ ; λ = 0,905, 0,488",analysis/python/tests/test_ch12.py,analysis/python/tests/test_ch12.py::TestExercices,voir solutions/ch12.tex,idem,verifie,2026-10-31 277 CALC-12-012,12,"Exercices 1, 3, 4, 5, 6 : 1 176 pi² = 109,25 m², 42,67 m ; 87,36 m², 171,52 m², 1,154 ; 2 042,86 $/m², 8 580 $, +8 523/−8 639 $, 15 600 $ ; +7,91 %, 10 447 $, 55 572 $ ; λ = 0,905, 0,488",analysis/python/tests/test_ch12.py,analysis/python/tests/test_ch12.py::TestExercices,voir solutions/ch12.tex,idem,verifie,2026-10-31
278+CALC-19-01,19,I de Moran des résidus (W knn8) : référence / sans secteur / sans secteur ni distance / ln P,analysis/python/cases/ch19_spatial.py,analysis/python/tests/test_ch19.py::test_moran_residus_avec_et_sans_secteur,-0.0136 (z -0.63) ; 0.3237 (z 17.11) ; 0.3595 ; 0.2013,idem (esda : z 17.11),verifie,2026-10-31
279+CALC-19-02,19,Corrélogramme des résidus sans secteur (bandes 0-0.5 et 2-3 km ; inversion au-delà de 8 km),analysis/python/cases/ch19_spatial.py,analysis/python/tests/test_ch19.py::test_correlogramme,0.225 ; 0.261 ; 12-15 km : -0.286,idem,verifie,2026-10-31
280+CALC-19-03,19,Tests LM sans secteur (LM-err/LM-lag/RLM-err/RLM-lag) et référence,analysis/python/cases/ch19_spatial.py,analysis/python/tests/test_ch19.py::test_tests_lm,285.18 / 116.21 / 170.06 / 1.09 (p 0.297) ; référence tous p > 0.1,idem (spreg identique),verifie,2026-10-31
281+CALC-19-04,19,SAR/SEM/SDM sans secteur par MV concentrée : rho 0.4385 (0.0412) ; lambda 0.6486 (0.0447) ; rho SDM 0.5089 ; logL 510.71/554.23/590.58/606.72 ; AIC -995.4/-1080.5/-1153.2/-1163.4 ; référence logL 650.00 AIC -1266.0,analysis/python/cases/ch19_spatial.py,analysis/python/tests/test_ch19.py::test_sar_sem_sdm,voir description,idem (spreg ML_Lag/ML_Error à 1e-6),verifie,2026-10-31
282+CALC-19-05,19,Effets directs/indirects/totaux ln_S : SAR 0.5715/0.4206/0.9921 (= beta/(1-rho)) ; SDM 0.5484/0.0666/0.6150,analysis/python/cases/ch19_spatial.py,analysis/python/tests/test_ch19.py::test_effets_directs_indirects,voir description,idem,verifie,2026-10-31
283+CALC-19-06,19,GWR noyau gaussien : bande CV 4.99 km (mgwr 4.87) ; tr(S) 44.8 ; AICc -1233.2 vs -994.8 (MCO) et -1264.9 (référence) ; R² 0.861 ; localisation par secteur vs référence vs générateur,analysis/python/cases/ch19_spatial.py,analysis/python/tests/test_ch19.py::test_gwr,Buckingham -0.313 / -0.344 / -0.342 ; Aylmer 0.003 / 0.057 / 0.062,idem,verifie,2026-10-31
284+CALC-19-07,19,Effets fixes : grilles 5/3/2 km (19/39/67 cellules) AIC -1252.1/-1240.4/-1225.4 ; 14 trimestres AIC -1253.8 ; indice 2026T3 0.199,analysis/python/cases/ch19_spatial.py,analysis/python/tests/test_ch19.py::test_effets_fixes,voir description,idem,verifie,2026-10-31
285+CALC-19-08,19,Variogramme sans secteur : gamma(0-1 km) 0.00699 ; max 0.01702 ; exponentiel pépite 0.00455 palier partiel 0.00897 portée 5.0 (borne) ; krigeage sujet -0.0597 (var 0.00567),analysis/python/cases/ch19_spatial.py,analysis/python/tests/test_ch19.py::test_variogramme_krigeage,voir description,idem,verifie,2026-10-31
286+CALC-19-09,19,Spatio-temporel (2 km / 12 mois) : 582 ventes retenues ; I 0.305 ; rho_st 0.3696 (0.0532) ; I résidus 0.128 ; SEM-st lambda 0.7485 AIC -1154.8,analysis/python/cases/ch19_spatial.py,analysis/python/tests/test_ch19.py::test_spatio_temporel,voir description,idem,verifie,2026-10-31
287+CALC-19-10,19,Validation croisée EQM log : 0.0841 / 0.1053 / 0.0878 / 0.0882 / 0.0950 / 0.0860,analysis/python/cases/ch19_spatial.py,analysis/python/tests/test_ch19.py::test_validation_croisee,voir description,idem,verifie,2026-10-31
288+CALC-19-11,19,Indications sujet : 637 953 / 664 342 / 624 799 / 633 352 / 654 429 / 627 519 $,analysis/python/cases/ch19_spatial.py,analysis/python/tests/test_ch19.py::test_indications_sujet,voir description,idem,verifie,2026-10-31
289+CALC-19-12,19,Sensibilité à W : rho 0.2359 (k=4) → 0.6537 (k=16) ; lambda 0.5066 → 0.7855 ; I stable 0.32-0.33,analysis/python/cases/ch19_spatial.py,analysis/python/tests/test_ch19.py::test_sensibilite,voir description,idem,verifie,2026-10-31
290+CALC-19-13,19,Solutions des exercices 19.1 (I = 0.6917 ; alternés -1) 19.3 19.4 19.5 19.6 (krigeage 0.0489 var 0.00869) 19.7 (SEM k=4/8/16 : 656 724 / 633 352 / 628 672 $),solutions/ch19.tex,analysis/python/tests/test_ch19.py::test_solutions_exercices et ::test_solution_exercice_7,voir description,idem,verifie,2026-10-31
291+CALC-19-14,19,Fonctions génériques ajoutées à traite.hedonique (sem_mv sar_mv tests_lm_spatial effets_sar gwr variogramme krigeage W spatio-temporelle),analysis/python/traite/hedonique.py,analysis/python/tests/test_ch19.py (8 tests de simulation),recouvrement des paramètres simulés ; identités (total = direct + indirect ; W_st nilpotente ; krigeage exact),idem,verifie,2026-10-31
added verification/chapters/ch11.md +44 −0
@@ -0,0 +1,44 @@
1+# Chapitre 11 — Inspection et pathologies du bâtiment : bilan de rédaction (2026-10-31)
2+
3+## Compteurs
4+- Prose : ≈ 10 200 mots (regex hors tableaux, figures, équations et commandes ; 14 300 mots bruts `wc`) ; 43 pages compilées (chapitre + solutions + bibliographie du chapitre).
5+- Figures : 5 — schéma TikZ de l'enveloppe (dans le .tex), `ch11-fig-frise-pathologies.pdf` (repères indicatifs, dates (v) vérifiées), `ch11-fig-matrice-age-effectif.pdf` (convention déclarée), `ch11-fig-fiche-etat.pdf` et `ch11-fig-sensibilite-age-effectif.pdf` (synthétiques, cas 1).
6+- Tableaux : 9 (deux inspections ; pathologies 1 et 2 ; échelle d'état ; fiche d'état ; travaux ; dépréciation ventilée ; sensibilité λ × état ; structure du formulaire).
7+- Équations numérotées : 8 (VEI, règle du test, âge effectif, indice d'état, courte durée, longue durée, majoration, D_phys du cas 1) ; 1 définition, 1 proposition démontrée (bornes, monotonie, invariance, écart ∝ λ).
8+- Exercices : 7 (niveaux 1, 1, 2, 2, 2, 3, 3) ; solutions détaillées dans `solutions/ch11.tex`, chiffres testés (`TestExercices`).
9+- Encadrés : objectifs, 2 × quebec (déclarations du vendeur ; codes et aides publiques), pratique (hypothèse extraordinaire), synthetique, exemple intégral (7 étapes), erreurs, limites, synthese.
10+- Citations : 22 clés, toutes dans `references/library.bib` (aucune nouvelle clé citée) ; 14 sources publiques nouvelles mentionnées en notes de bas de page avec URL et date (lib-ch11.bib, 13 entrées non citées ; ch11-registry.csv, 14 lignes SRC-11-001 à 014).
11+
12+## Contrôles
13+- `tools/compile_chapter.sh 11` → `OK ch11 : 43 pages, 0 references/citations non definies, 0 overfull hbox`. Avertissements restants : biblatex « No driver for 'jurisdiction'/'legislation' » (types d'entrées existants de library.bib, communs aux autres chapitres) ; un `Overfull \vbox` de 0,34 pt.
14+- `python3 -m pytest -q analysis/python/tests/test_ch11.py` → 15 passed.
15+- `python3 tools/check_manuscript.py --no-log` : seules remarques pour ch11 = figures non inscrites dans `verification/figures.csv` global (attendu : elles sont dans `verification/chapters/ch11-figures.csv`, règle de collision).
16+- Scripts : `analysis/python/cases/ch11_inspection.py` (imprime tous les chiffres ; réutilise `ch21_cout_neuf.cout_neuf_sujet` pour C_N = 397 567,33 $ et `traite.cout` : `Composante`, `depreciation_ventilee`, `depreciation_age_vie`, `vie_restante`) ; `analysis/python/figures/ch11_{matrice_age_effectif,frise_pathologies,fiche_etat}.py` (idempotents, `traite.style`). Aucune modification de `traite/*.py`.
17+- PDF relu : planche de contact des 43 pages + pages 6 (TikZ), 7, 8, 9, 16, 19, 23, 24, 26, 30 à 70 dpi. Deux pages de figures flottantes (19, 24) laissent du blanc ; aucun débordement.
18+- Convention `\cref` sans article : toutes les occurrences portent article/préposition écrits (grep vérifié).
19+
20+## Fichiers livrés
21+`manuscript/chapters/ch11.tex`, `solutions/ch11.tex`, `analysis/python/cases/ch11_inspection.py`, `analysis/python/figures/ch11_*.py`, `analysis/python/tests/test_ch11.py`, `assets/figures/ch11/*.pdf` (4), `verification/chapters/ch11-{figures,calculations,claims,registry}.csv`, `references/lib-ch11.bib`, ce bilan. Rien n'a été écrit dans les CSV globaux ni dans `library.bib`.
22+
23+## Conventions introduites (à reporter dans l'annexe des conventions de calcul lors de l'harmonisation)
24+- Échelle d'état 1-5 et fractions φ(c) = 0,05 / 0,25 / 0,50 / 0,75 / 1,00.
25+- Âge effectif : a_eff = (1 − λ)·a + λ·φ(c)·L, borné à [0, L], λ = 0,5 (convention de l'ouvrage).
26+- Coût neuf d'une composante = coût direct du devis × C_N/D (1,33043 pour le cas 1).
27+- Vie économique du bâtiment du cas 1 : 60 ans (hypothèse) ; âge effectif de la structure : 29 ans ; vie restante 31 ans.
28+
29+## Points non vérifiés ou partiellement vérifiés (voir ch11-claims.csv)
30+1. Vies utiles des composantes et fractions φ(c) : hypothèses pédagogiques, aucune source publique (CLM-11-023, non_verifie, déclaré dans le texte).
31+2. Frise des pathologies par période : repères de connaissances générales, non sourcés sauf dates (v) (CLM-11-022, non_verifie, signalé dans la figure, la légende et les limites).
32+3. Pratique du test de pyrite (protocole d'échantillonnage, IPPG, certificats de remblai depuis la fin des années 1990) : sources secondaires seulement, aucune page gouvernementale trouvée (CLM-11-016, partiellement_verifie) ; formulé sans nom de protocole ni seuil.
33+4. Encadrement réglementaire des inspecteurs en bâtiment (Loi 16, 2019) : intitulé vérifié, état du règlement d'application non vérifié (pages RBQ/Québec.ca 404) — signalé dans le texte et les limites (CLM-11-006).
34+5. Incidence des matériaux anciens (aluminium, galvanisé, polybutylène) sur l'assurabilité : connaissance de pratique, qualitatif (CLM-11-024, non_verifie).
35+6. Paramètres de la valeur de l'information autres que le dosimètre à ≈ 50 $ (Québec.ca) : paramètres d'illustration déclarés (CLM-11-025).
36+7. Aucune prévalence chiffrée de pathologie n'est citée (choix délibéré : l'estimation INSPQ « environ 10 % » de bâtiments au-dessus de 200 Bq/m³ n'a pas été reprise, source ancienne et non reconfirmée sur Québec.ca).
37+8. Pages Santé Canada/RBQ : aucune « date de modification » formelle pour les pages RBQ (seule la date d'impression) ; dates « Détails de la page » utilisées pour Canada.ca.
38+
39+## Suggestions pour l'harmonisation
40+- Le ch. 24 doit partir de la fiche d'état (âge effectif de la structure 29 ans, D_phys ventilée 221 089,88 $ = 55,61 % de C_N, curable 10 479,98 $, courte durée 130 584,55 $) et y ajouter les désuétudes ; `ch11_inspection.depreciation()` peut être importé directement. Le ch. 45 réconcilie.
41+- Le ch. 17 peut utiliser l'indice d'état pondéré I_E = 2,81 comme variable d'état du sujet.
42+- L'annexe « Formulaires d'inspection » doit suivre le plan en 9 sections du tableau 11.9 et sa synthèse chiffrée doit reprendre les colonnes de la fiche (année, âge, L, note, coût neuf).
43+- Si `library.bib` intègre `lib-ch11.bib`, remplacer les notes de bas de page des §11.1.2, 11.2.4, 11.3.3, 11.4 par des `\parencite` (clés `aibqNorme2011`, `internachiSOP2022`, `scRadonLignes2025`, `scRadonMesure2026`, `qcRadonDomiciliaire2026`, `scMoisissure2023`, `scAmiante2023`, `rncanRenovations2025`, `rbqOcre`, `rbqPyritePyrrhotite`, `rbqSolsArgileux`, `rbqEquipementsPetroliers`, `qcPyrrhotiteVoletI2026`).
44+- Le pied de page du script de compilation signale « No driver for 'jurisdiction'/'legislation' » : définir ces types dans le style biblatex ou les convertir (`@misc`) lors de l'harmonisation (concerne `marcouxPicard2008`, `abbDomtar2007`, `loi16_2019`).
added verification/chapters/ch19-calculations.csv +15 −0
@@ -0,0 +1,15 @@
1+id,chapitre,description,script,test,resultat_attendu,resultat_obtenu,statut,date
2+CALC-19-01,19,I de Moran des résidus (W knn8) : référence / sans secteur / sans secteur ni distance / ln P,analysis/python/cases/ch19_spatial.py,analysis/python/tests/test_ch19.py::test_moran_residus_avec_et_sans_secteur,"-0.0136 (z -0.63) ; 0.3237 (z 17.11) ; 0.3595 ; 0.2013","idem (esda : z 17.11)",verifie,2026-10-31
3+CALC-19-02,19,Corrélogramme des résidus sans secteur (bandes 0-0.5 et 2-3 km ; inversion au-delà de 8 km),analysis/python/cases/ch19_spatial.py,analysis/python/tests/test_ch19.py::test_correlogramme,"0.225 ; 0.261 ; 12-15 km : -0.286",idem,verifie,2026-10-31
4+CALC-19-03,19,Tests LM sans secteur (LM-err/LM-lag/RLM-err/RLM-lag) et référence,analysis/python/cases/ch19_spatial.py,analysis/python/tests/test_ch19.py::test_tests_lm,"285.18 / 116.21 / 170.06 / 1.09 (p 0.297) ; référence tous p > 0.1","idem (spreg identique)",verifie,2026-10-31
5+CALC-19-04,19,SAR/SEM/SDM sans secteur par MV concentrée : rho 0.4385 (0.0412) ; lambda 0.6486 (0.0447) ; rho SDM 0.5089 ; logL 510.71/554.23/590.58/606.72 ; AIC -995.4/-1080.5/-1153.2/-1163.4 ; référence logL 650.00 AIC -1266.0,analysis/python/cases/ch19_spatial.py,analysis/python/tests/test_ch19.py::test_sar_sem_sdm,voir description,"idem (spreg ML_Lag/ML_Error à 1e-6)",verifie,2026-10-31
6+CALC-19-05,19,Effets directs/indirects/totaux ln_S : SAR 0.5715/0.4206/0.9921 (= beta/(1-rho)) ; SDM 0.5484/0.0666/0.6150,analysis/python/cases/ch19_spatial.py,analysis/python/tests/test_ch19.py::test_effets_directs_indirects,voir description,idem,verifie,2026-10-31
7+CALC-19-06,19,GWR noyau gaussien : bande CV 4.99 km (mgwr 4.87) ; tr(S) 44.8 ; AICc -1233.2 vs -994.8 (MCO) et -1264.9 (référence) ; R² 0.861 ; localisation par secteur vs référence vs générateur,analysis/python/cases/ch19_spatial.py,analysis/python/tests/test_ch19.py::test_gwr,"Buckingham -0.313 / -0.344 / -0.342 ; Aylmer 0.003 / 0.057 / 0.062",idem,verifie,2026-10-31
8+CALC-19-07,19,Effets fixes : grilles 5/3/2 km (19/39/67 cellules) AIC -1252.1/-1240.4/-1225.4 ; 14 trimestres AIC -1253.8 ; indice 2026T3 0.199,analysis/python/cases/ch19_spatial.py,analysis/python/tests/test_ch19.py::test_effets_fixes,voir description,idem,verifie,2026-10-31
9+CALC-19-08,19,Variogramme sans secteur : gamma(0-1 km) 0.00699 ; max 0.01702 ; exponentiel pépite 0.00455 palier partiel 0.00897 portée 5.0 (borne) ; krigeage sujet -0.0597 (var 0.00567),analysis/python/cases/ch19_spatial.py,analysis/python/tests/test_ch19.py::test_variogramme_krigeage,voir description,idem,verifie,2026-10-31
10+CALC-19-09,19,Spatio-temporel (2 km / 12 mois) : 582 ventes retenues ; I 0.305 ; rho_st 0.3696 (0.0532) ; I résidus 0.128 ; SEM-st lambda 0.7485 AIC -1154.8,analysis/python/cases/ch19_spatial.py,analysis/python/tests/test_ch19.py::test_spatio_temporel,voir description,idem,verifie,2026-10-31
11+CALC-19-10,19,Validation croisée EQM log : 0.0841 / 0.1053 / 0.0878 / 0.0882 / 0.0950 / 0.0860,analysis/python/cases/ch19_spatial.py,analysis/python/tests/test_ch19.py::test_validation_croisee,voir description,idem,verifie,2026-10-31
12+CALC-19-11,19,Indications sujet : 637 953 / 664 342 / 624 799 / 633 352 / 654 429 / 627 519 $,analysis/python/cases/ch19_spatial.py,analysis/python/tests/test_ch19.py::test_indications_sujet,voir description,idem,verifie,2026-10-31
13+CALC-19-12,19,Sensibilité à W : rho 0.2359 (k=4) → 0.6537 (k=16) ; lambda 0.5066 → 0.7855 ; I stable 0.32-0.33,analysis/python/cases/ch19_spatial.py,analysis/python/tests/test_ch19.py::test_sensibilite,voir description,idem,verifie,2026-10-31
14+CALC-19-13,19,Solutions des exercices 19.1 (I = 0.6917 ; alternés -1) 19.3 19.4 19.5 19.6 (krigeage 0.0489 var 0.00869) 19.7 (SEM k=4/8/16 : 656 724 / 633 352 / 628 672 $),solutions/ch19.tex,analysis/python/tests/test_ch19.py::test_solutions_exercices et ::test_solution_exercice_7,voir description,idem,verifie,2026-10-31
15+CALC-19-14,19,Fonctions génériques ajoutées à traite.hedonique (sem_mv sar_mv tests_lm_spatial effets_sar gwr variogramme krigeage W spatio-temporelle),analysis/python/traite/hedonique.py,analysis/python/tests/test_ch19.py (8 tests de simulation),"recouvrement des paramètres simulés ; identités (total = direct + indirect ; W_st nilpotente ; krigeage exact)",idem,verifie,2026-10-31
added verification/chapters/ch19-claims.csv +11 −0
@@ -0,0 +1,11 @@
1+id,chapitre,affirmation,source_id,verification,date,statut,notes
2+CLM-19-01,19,Le générateur du cas 1 contient une prime de secteur (log du rapport des médianes APCIQ T3 2026 à Hull ; Masson-Angers − 0,02) et un gradient −0,006/km au-delà de 3 km ; bruit iid σ = 0,085 ; aucun processus spatial résiduel,,data/synthetic/README.md (lois de génération),2026-10-31,verifie,hypothèses pédagogiques déclarées du dépôt
3+CLM-19-02,19,Rôles d'évaluation foncière du Québec : 1 140 municipalités en format ouvert CC-BY 4.0 avec versions géoréférencées FGDB/GPKG/GeoJSON ; noms et adresses caviardés,SRC-B-013,library.bib (dqroles) et registry.csv SRC-B-013 (Données Québec consulté le 2026-10-31),2026-10-31,verifie,
4+CLM-19-03,19,Règlement 994-2025 de Gatineau : taux de base 5,224 $/1 000 $ ; taux résidentiel par unité de voisinage de 1,613 à 7,544 $/1 000 $ (annexe),SRC-E-028,notes-E.md §taxes 2026 [O] et library.bib (gatineaureglement994),2026-10-31,verifie,le motif de la variation par unité de voisinage n'est pas commenté dans le chapitre
5+CLM-19-04,19,Les NPP de l'OEAQ exigent la description des données de l'analyse et du raisonnement menant à la conclusion,SRC-C-001,notes-C.md §2 (règle 2.3 : éléments du rapport),2026-10-31,verifie,formulation générale ; renvoi au ch. 10
6+CLM-19-05,19,Les normes IAAO sur l'évaluation de masse (2025) et sur les AVM (2018) traitent la localisation comme dimension à modéliser et à valider par études de ratios,,library.bib (iaaomass2025 ; iaaoavm2018) — documents consultés le 2026-10-31 par le dossier C ; contenu détaillé des sections non relu pour ce chapitre,2026-10-31,partiellement_verifie,affirmation formulée de façon générale
7+CLM-19-06,19,Les tests LM robustes sont dus à Anselin Bera Florax et Yoon (1996),,article non présent dans library.bib ; présentation dans elhorst2014 (cité),2026-10-31,non_verifie,le texte renvoie à Elhorst 2014 et n'ajoute pas la référence 1996
8+CLM-19-07,19,Version corrigée de la statistique de Moran sur résidus MCO : 17,3 contre 17,1 pour le cas 1,,spreg.OLS(moran=True) vs traite.hedonique.moran_i (contrôle croisé dans ch19_spatial.py --controle),2026-10-31,verifie,
9+CLM-19-08,19,Travaux de Québec : Des Rosiers–Thériault–Villeneuve 2000 (accès/voisinage) ; Thériault et al. 2003 (prix implicites variant avec la localisation) ; Kestens et al. 2006 (profil des acheteurs) ; Dubé–Legros 2013 (données de Québec),SRC-D-*,notes-D.md §ch. 18-19 et 38 (entrées vérifiées lib-D),2026-10-31,verifie,résumés des titres/notes-D ; aucun chiffre tiré de ces articles
10+CLM-19-09,19,Un degré de longitude vaut ≈ 78 km à la latitude de Gatineau et un degré de latitude ≈ 111 km,,calcul : 111,32 × cos(45,4°) = 78,2 km,2026-10-31,verifie,même convention que ch16_selection.py (111 / 78)
11+CLM-19-10,19,Portées de dépendance de plusieurs kilomètres (Basu–Thibodeau 1998 Dallas) ; anisotropie (Gillen–Thibodeau–Wachter 2001),SRC-D-*,notes-D.md §ch. 19 (titres et résumés),2026-10-31,partiellement_verifie,ordre de grandeur cité sans chiffre précis
added verification/chapters/ch19-figures.csv +9 −0
@@ -0,0 +1,9 @@
1+fichier,chapitre,script,donnees,type_donnees,legende_courte,statut
2+ch19/ch19-fig-carte-residus.pdf,19,analysis/python/figures/ch19_figures.py,data/synthetic/cas1_gatineau_ventes.csv,synthetique,"Cartes des résidus sans indicatrices de secteur et du modèle de référence sur coordonnées synthétiques [données synthétiques]",verifie
3+ch19/ch19-fig-correlogramme.pdf,19,analysis/python/figures/ch19_figures.py,data/synthetic/cas1_gatineau_ventes.csv,synthetique,"Corrélogramme : I de Moran des résidus par bande de distance [données synthétiques]",verifie
4+ch19/ch19-fig-gwr-coefficients.pdf,19,analysis/python/figures/ch19_figures.py,data/synthetic/cas1_gatineau_ventes.csv,synthetique,"Coefficients locaux de la GWR : localisation, superficie, âge [données synthétiques]",verifie
5+ch19/ch19-fig-gwr-bande.pdf,19,analysis/python/figures/ch19_figures.py,data/synthetic/cas1_gatineau_ventes.csv,synthetique,"Score de validation croisée de la GWR selon la bande passante [données synthétiques]",verifie
6+ch19/ch19-fig-variogramme.pdf,19,analysis/python/figures/ch19_figures.py,data/synthetic/cas1_gatineau_ventes.csv,synthetique,"Variogramme empirique des résidus et modèle exponentiel [données synthétiques]",verifie
7+ch19/ch19-fig-krigeage.pdf,19,analysis/python/figures/ch19_figures.py,data/synthetic/cas1_gatineau_ventes.csv,synthetique,"Surface krigée des résidus du modèle sans secteur [données synthétiques]",verifie
8+ch19/ch19-fig-sensibilite-W.pdf,19,analysis/python/figures/ch19_figures.py,data/synthetic/cas1_gatineau_ventes.csv,synthetique,"I de Moran, rho du SAR et lambda du SEM selon le nombre de voisins [données synthétiques]",verifie
9+ch19/tikz-taxonomie-effets-spatiaux,19,manuscript/chapters/ch19.tex,,schema,"Taxonomie dépendance / hétérogénéité spatiales et outils (TikZ)",verifie
added verification/chapters/ch19-registry.csv +1 −0
@@ -0,0 +1 @@
1+id,type,titre,organisme,version_ou_date,url,date_consultation,statut,utilise_dans,notes
modified verification/claims.csv +10 −0
@@ -492,3 +492,13 @@ CLM-12-016,12,"Sirmans, Macpherson & Zietz (2005) : recension de 125 études ; l
492 CLM-12-017,12,"Élasticité valeur/superficie du cas 1 = 0,539 (ET HC1 0,013) ; V sujet = 637 953 $ ; σ résiduel 0,083",,traite.cas1.hedonique / predire (données synthétiques),2026-10-31,verifie,Propriété du jeu synthétique 492 CLM-12-017,12,"Élasticité valeur/superficie du cas 1 = 0,539 (ET HC1 0,013) ; V sujet = 637 953 $ ; σ résiduel 0,083",,traite.cas1.hedonique / predire (données synthétiques),2026-10-31,verifie,Propriété du jeu synthétique
493 CLM-12-018,12,"Atténuation classique des erreurs sur les variables : plim β̂ = λβ, λ = Var(ẽ)/(Var(ẽ)+σu²) (Wooldridge 2010, chap. 4)",SRC-D (lib-D),Résultat standard ; démonstration dans le chapitre ; simulation test_ch12.py,2026-10-31,verifie, 493 CLM-12-018,12,"Atténuation classique des erreurs sur les variables : plim β̂ = λβ, λ = Var(ẽ)/(Var(ẽ)+σu²) (Wooldridge 2010, chap. 4)",SRC-D (lib-D),Résultat standard ; démonstration dans le chapitre ; simulation test_ch12.py,2026-10-31,verifie,
494 CLM-12-019,12,Nuages de points lidar disponibles pour une large partie du Québec habité ; usage pour les rôles et les zones inondables,,Connaissance générale ; aucune source consultée dans ce lot,2026-10-31,non_verifie,Formulé sans chiffre ; à sourcer en harmonisation (MRNF lidar) ou à atténuer 494 CLM-12-019,12,Nuages de points lidar disponibles pour une large partie du Québec habité ; usage pour les rôles et les zones inondables,,Connaissance générale ; aucune source consultée dans ce lot,2026-10-31,non_verifie,Formulé sans chiffre ; à sourcer en harmonisation (MRNF lidar) ou à atténuer
495+CLM-19-01,19,Le générateur du cas 1 contient une prime de secteur (log du rapport des médianes APCIQ T3 2026 à Hull ; Masson-Angers − 0,02) et un gradient −0,006/km au-delà de 3 km ; bruit iid σ = 0,085 ; aucun processus spatial résiduel,,data/synthetic/README.md (lois de génération)
496+CLM-19-02,19,Rôles d'évaluation foncière du Québec : 1 140 municipalités en format ouvert CC-BY 4.0 avec versions géoréférencées FGDB/GPKG/GeoJSON ; noms et adresses caviardés,SRC-B-013,library.bib (dqroles) et registry.csv SRC-B-013 (Données Québec consulté le 2026-10-31),2026-10-31,verifie,
497+CLM-19-03,19,Règlement 994-2025 de Gatineau : taux de base 5,224 $/1 000 $ ; taux résidentiel par unité de voisinage de 1,613 à 7,544 $/1 000 $ (annexe),SRC-E-028,notes-E.md §taxes 2026 [O] et library.bib (gatineaureglement994)
498+CLM-19-04,19,Les NPP de l'OEAQ exigent la description des données de l'analyse et du raisonnement menant à la conclusion,SRC-C-001,notes-C.md §2 (règle 2.3 : éléments du rapport),2026-10-31,verifie,formulation générale ; renvoi au ch. 10
499+CLM-19-05,19,Les normes IAAO sur l'évaluation de masse (2025) et sur les AVM (2018) traitent la localisation comme dimension à modéliser et à valider par études de ratios,,library.bib (iaaomass2025 ; iaaoavm2018) — documents consultés le 2026-10-31 par le dossier C ; contenu détaillé des sections non relu pour ce chapitre,2026-10-31,partiellement_verifie,affirmation formulée de façon générale
500+CLM-19-06,19,Les tests LM robustes sont dus à Anselin Bera Florax et Yoon (1996),,article non présent dans library.bib ; présentation dans elhorst2014 (cité),2026-10-31,non_verifie,le texte renvoie à Elhorst 2014 et n'ajoute pas la référence 1996
501+CLM-19-07,19,Version corrigée de la statistique de Moran sur résidus MCO : 17,3 contre 17,1 pour le cas 1,,spreg.OLS(moran=True) vs traite.hedonique.moran_i (contrôle croisé dans ch19_spatial.py --controle),2026-10-31
502+CLM-19-08,19,Travaux de Québec : Des Rosiers–Thériault–Villeneuve 2000 (accès/voisinage) ; Thériault et al. 2003 (prix implicites variant avec la localisation) ; Kestens et al. 2006 (profil des acheteurs) ; Dubé–Legros 2013 (données de Québec),SRC-D-*,notes-D.md §ch. 18-19 et 38 (entrées vérifiées lib-D),2026-10-31,verifie,résumés des titres/notes-D ; aucun chiffre tiré de ces articles
503+CLM-19-09,19,Un degré de longitude vaut ≈ 78 km à la latitude de Gatineau et un degré de latitude ≈ 111 km,,calcul : 111,32 × cos(45,4°) = 78,2 km
504+CLM-19-10,19,Portées de dépendance de plusieurs kilomètres (Basu–Thibodeau 1998 Dallas) ; anisotropie (Gillen–Thibodeau–Wachter 2001),SRC-D-*,notes-D.md §ch. 19 (titres et résumés),2026-10-31,partiellement_verifie,ordre de grandeur cité sans chiffre précis
modified verification/figures.csv +8 −0
@@ -96,3 +96,11 @@ ch12/ch12-fig-erreur-valeur.pdf,12,analysis/python/figures/ch12_figures.py,data/
96 ch12/ch12-fig-attenuation.pdf,12,analysis/python/figures/ch12_figures.py,data/synthetic/cas1_gatineau_ventes.csv,synthetique,Atténuation du coefficient de la superficie par une erreur de mesure (théorie et 200 simulations) et aplatissement du gradient prix-taille [données synthétiques],verifie 96 ch12/ch12-fig-attenuation.pdf,12,analysis/python/figures/ch12_figures.py,data/synthetic/cas1_gatineau_ventes.csv,synthetique,Atténuation du coefficient de la superficie par une erreur de mesure (théorie et 200 simulations) et aplatissement du gradient prix-taille [données synthétiques],verifie
97 ch12/plan-cote-sujet (TikZ),12,manuscript/chapters/ch12.tex,analysis/python/cases/ch12_mesurage.py,schema,"Plan coté reconstitué du sujet du cas 1 : rez-de-chaussée et étage, cotes extérieures et intérieures, trémie [données synthétiques]",verifie 97 ch12/plan-cote-sujet (TikZ),12,manuscript/chapters/ch12.tex,analysis/python/cases/ch12_mesurage.py,schema,"Plan coté reconstitué du sujet du cas 1 : rez-de-chaussée et étage, cotes extérieures et intérieures, trémie [données synthétiques]",verifie
98 ch12/plan-implantation-sujet (TikZ),12,manuscript/chapters/ch12.tex,analysis/python/cases/ch12_mesurage.py,schema,Plan d'implantation du sujet sur son lot de 620 m² à la manière d'un certificat de localisation (fictif) [données synthétiques],verifie 98 ch12/plan-implantation-sujet (TikZ),12,manuscript/chapters/ch12.tex,analysis/python/cases/ch12_mesurage.py,schema,Plan d'implantation du sujet sur son lot de 620 m² à la manière d'un certificat de localisation (fictif) [données synthétiques],verifie
99+ch19/ch19-fig-carte-residus.pdf,19,analysis/python/figures/ch19_figures.py,data/synthetic/cas1_gatineau_ventes.csv,synthetique,Cartes des résidus sans indicatrices de secteur et du modèle de référence sur coordonnées synthétiques [données synthétiques],verifie
100+ch19/ch19-fig-correlogramme.pdf,19,analysis/python/figures/ch19_figures.py,data/synthetic/cas1_gatineau_ventes.csv,synthetique,Corrélogramme : I de Moran des résidus par bande de distance [données synthétiques],verifie
101+ch19/ch19-fig-gwr-coefficients.pdf,19,analysis/python/figures/ch19_figures.py,data/synthetic/cas1_gatineau_ventes.csv,synthetique,"Coefficients locaux de la GWR : localisation, superficie, âge [données synthétiques]",verifie
102+ch19/ch19-fig-gwr-bande.pdf,19,analysis/python/figures/ch19_figures.py,data/synthetic/cas1_gatineau_ventes.csv,synthetique,Score de validation croisée de la GWR selon la bande passante [données synthétiques],verifie
103+ch19/ch19-fig-variogramme.pdf,19,analysis/python/figures/ch19_figures.py,data/synthetic/cas1_gatineau_ventes.csv,synthetique,Variogramme empirique des résidus et modèle exponentiel [données synthétiques],verifie
104+ch19/ch19-fig-krigeage.pdf,19,analysis/python/figures/ch19_figures.py,data/synthetic/cas1_gatineau_ventes.csv,synthetique,Surface krigée des résidus du modèle sans secteur [données synthétiques],verifie
105+ch19/ch19-fig-sensibilite-W.pdf,19,analysis/python/figures/ch19_figures.py,data/synthetic/cas1_gatineau_ventes.csv,synthetique,"I de Moran, rho du SAR et lambda du SEM selon le nombre de voisins [données synthétiques]",verifie
106+ch19/tikz-taxonomie-effets-spatiaux,19,manuscript/chapters/ch19.tex,,schema,Taxonomie dépendance / hétérogénéité spatiales et outils (TikZ),verifie
99 107
Menu

Theme