Ch. 47 livré (50 p., dossier complet, Monte-Carlo) ; renvois normalisés
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
60 changed files +5,653 −58
added
analysis/python/cases/ch02_droits.py
+176 −0
@@ -0,0 +1,176 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Chapitre 2 — Droits immobiliers et bases de valeur : calculs de l'exemple intégral sur le cas 3 (12 logements, Montréal, | |
| 3 | +DONNÉES SYNTHÉTIQUES). Valeur du droit du locateur (baux en place) vs valeur de la pleine propriété libre de baux (loyers de | |
| 4 | +marché) ; valeur des baux pour les locataires ; sensibilité à la rotation et au taux d'actualisation ; chiffres des exercices. | |
| 5 | +Toutes les grandeurs du cas 3 proviennent de `traite.cas3` (module partagé) ; les formules financières de `traite.finance`. | |
| 6 | +Usage (racine du dépôt) : PYTHONPATH=analysis/python python3 analysis/python/cases/ch02_droits.py""" | |
| 7 | +from __future__ import annotations | |
| 8 | +import sys, copy, pathlib | |
| 9 | +import numpy as np, pandas as pd | |
| 10 | +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1])) | |
| 11 | +from traite import cas3, finance as fin | |
| 12 | + | |
| 13 | +ROTATIONS = [0.0, 1 / 12, 2 / 12, 3 / 12, 6 / 12] # 0, 1, 2, 3, 6 logements reloués par an (le module arrondit ρ × 12 à l'entier) | |
| 14 | +TAUX = [0.0625, 0.0725, 0.0825] | |
| 15 | + | |
| 16 | + | |
| 17 | +# ----------------------------------------------------------------------------- écart contractuel / marché | |
| 18 | + | |
| 19 | +def etat_marche(etat: pd.DataFrame) -> pd.DataFrame: | |
| 20 | + """État locatif hypothétique « libre de baux » : chaque logement est reloué au loyer de marché dès l'année 1.""" | |
| 21 | + e = etat.copy() | |
| 22 | + e["loyer_mensuel_contrat"] = e["loyer_mensuel_marche"].astype(float) | |
| 23 | + return e | |
| 24 | + | |
| 25 | + | |
| 26 | +def ecart_par_logement(etat: pd.DataFrame, annee_eval: int = 2026) -> pd.DataFrame: | |
| 27 | + e = etat.copy() | |
| 28 | + e["ecart_mensuel"] = e["loyer_mensuel_marche"] - e["loyer_mensuel_contrat"] | |
| 29 | + e["ecart_annuel"] = 12 * e["ecart_mensuel"] | |
| 30 | + e["ratio_contrat_marche"] = e["loyer_mensuel_contrat"] / e["loyer_mensuel_marche"] | |
| 31 | + e["anciennete"] = annee_eval - e["locataire_depuis"] | |
| 32 | + return e[["logement", "pieces", "locataire_depuis", "anciennete", "loyer_mensuel_contrat", "loyer_mensuel_marche", | |
| 33 | + "ecart_mensuel", "ecart_annuel", "ratio_contrat_marche"]] | |
| 34 | + | |
| 35 | + | |
| 36 | +# ----------------------------------------------------------------------------- formules fermées (équations du chapitre) | |
| 37 | + | |
| 38 | +def valeur_bail_locataire(ecart_annuel: float, n: int, r: float) -> float: | |
| 39 | + """Valeur du bail pour le locataire = Σ_{t=1}^{n} (L_marché − L_contrat)/(1+r)^t, écart constant (éq. annuité).""" | |
| 40 | + return ecart_annuel * fin.facteur_va_annuite(r, n) | |
| 41 | + | |
| 42 | + | |
| 43 | +def valeur_droit_temporaire_et_reversion(revenu_annuel: float, n: int, r: float, valeur_reversion: float) -> dict: | |
| 44 | + """Décomposition d'un droit temporaire (usufruit, bail au locateur) et de la réversion (nue-propriété).""" | |
| 45 | + va_droit = revenu_annuel * fin.facteur_va_annuite(r, n) | |
| 46 | + va_rev = valeur_reversion * fin.facteur_va(r, n) | |
| 47 | + return {"droit_temporaire": va_droit, "reversion": va_rev, "total": va_droit + va_rev} | |
| 48 | + | |
| 49 | + | |
| 50 | +def usufruit_nue_propriete(revenu_annee1: float, g: float, r: float, n: int) -> dict: | |
| 51 | + """Usufruit de n ans sur un bien dont le revenu net croît au taux g : VA de l'annuité croissante ; nue-propriété = réversion | |
| 52 | + V_n = R_{n+1}/(r−g) actualisée ; la somme redonne la pleine propriété V_0 = R_1/(r−g) (identité de Gordon).""" | |
| 53 | + flux = [revenu_annee1 * (1 + g) ** (t - 1) for t in range(1, n + 1)] | |
| 54 | + va_usufruit = fin.van([0.0] + flux, r) | |
| 55 | + v_pleine = revenu_annee1 / (r - g) | |
| 56 | + v_n = revenu_annee1 * (1 + g) ** n / (r - g) | |
| 57 | + va_nue = v_n * fin.facteur_va(r, n) | |
| 58 | + return {"V_pleine": v_pleine, "usufruit": va_usufruit, "nue_propriete": va_nue, "somme": va_usufruit + va_nue, "V_n": v_n, | |
| 59 | + "part_usufruit": va_usufruit / v_pleine} | |
| 60 | + | |
| 61 | + | |
| 62 | +def va_ecart_rotation(ecart_net_annee1: float, r: float, g: float, rho: float, T: int | None = None) -> float: | |
| 63 | + """Valeur actuelle de l'écart contractuel/marché sous rotation ρ : écart_t = Δ_1 [(1−ρ)(1+g)]^{t−1}. | |
| 64 | + Perpétuité (T=None) : Δ_1 / (r − g + ρ(1+g)) ; sinon somme finie.""" | |
| 65 | + q = (1 - rho) * (1 + g) / (1 + r) | |
| 66 | + if T is None: | |
| 67 | + if q >= 1: | |
| 68 | + raise ValueError("série divergente : (1−ρ)(1+g) ≥ 1+r") | |
| 69 | + return ecart_net_annee1 / (1 + r) / (1 - q) | |
| 70 | + return ecart_net_annee1 / (1 + r) * (1 - q ** T) / (1 - q) if abs(1 - q) > 1e-14 else ecart_net_annee1 * T / (1 + r) | |
| 71 | + | |
| 72 | + | |
| 73 | +# ----------------------------------------------------------------------------- décomposition par le module partagé | |
| 74 | + | |
| 75 | +def decomposition(d: dict, rotation: float | None = None, taux: float | None = None) -> dict: | |
| 76 | + """V_pleine (libre de baux, loyers de marché) − V_locateur (baux en place) = valeur agrégée des baux pour les locataires. | |
| 77 | + Les deux valeurs sont des DCF de `traite.cas3` avec les mêmes hypothèses (dépenses, CAPEX, taux terminal, coûts de vente).""" | |
| 78 | + hyp = copy.deepcopy(d["hyp"]) | |
| 79 | + if rotation is not None: | |
| 80 | + hyp["rotation_annuelle"] = rotation | |
| 81 | + if taux is not None: | |
| 82 | + hyp["taux_actualisation"] = taux | |
| 83 | + proj_c = cas3.projection_dcf(d["etat"], d["dep"], hyp) | |
| 84 | + proj_m = cas3.projection_dcf(etat_marche(d["etat"]), d["dep"], hyp) | |
| 85 | + v_c = cas3.valeur_dcf(proj_c, hyp); v_m = cas3.valeur_dcf(proj_m, hyp) | |
| 86 | + rne_c = cas3.rne(d["etat"], d["dep"], hyp); rne_m = cas3.rne(etat_marche(d["etat"]), d["dep"], hyp) | |
| 87 | + return {"rotation": hyp["rotation_annuelle"], "taux": hyp["taux_actualisation"], "proj_contrat": proj_c, "proj_marche": proj_m, | |
| 88 | + "V_locateur": v_c["valeur"], "V_pleine": v_m["valeur"], "V_locataires": v_m["valeur"] - v_c["valeur"], | |
| 89 | + "part_locataires": (v_m["valeur"] - v_c["valeur"]) / v_m["valeur"], | |
| 90 | + "va_flux_contrat": v_c["va_flux"], "va_flux_marche": v_m["va_flux"], "va_vt_contrat": v_c["va_terminale"], "va_vt_marche": v_m["va_terminale"], | |
| 91 | + "rne1_contrat": rne_c["rne"], "rne1_marche": rne_m["rne"], "rbe_contrat": rne_c["rbe"], "rbe_marche": rne_m["rbe"]} | |
| 92 | + | |
| 93 | + | |
| 94 | +def capitalisation_naive(dec: dict, taux_cap: float) -> dict: | |
| 95 | + """Erreur classique : capitaliser l'écart de RNE de l'année 1 à perpétuité (comme si les baux ne se renouvelaient jamais au marché).""" | |
| 96 | + v_c = fin.capitalisation_directe(dec["rne1_contrat"], taux_cap); v_m = fin.capitalisation_directe(dec["rne1_marche"], taux_cap) | |
| 97 | + return {"Vcap_contrat": v_c, "Vcap_marche": v_m, "ecart_capitalise": v_m - v_c} | |
| 98 | + | |
| 99 | + | |
| 100 | +def sensibilite(d: dict) -> pd.DataFrame: | |
| 101 | + rows = [] | |
| 102 | + for rho in ROTATIONS: | |
| 103 | + for r in TAUX: | |
| 104 | + dec = decomposition(d, rotation=rho, taux=r) | |
| 105 | + rows.append({"rotation": rho, "taux": r, "V_locateur": dec["V_locateur"], "V_pleine": dec["V_pleine"], | |
| 106 | + "V_locataires": dec["V_locataires"], "part": dec["part_locataires"]}) | |
| 107 | + return pd.DataFrame(rows) | |
| 108 | + | |
| 109 | + | |
| 110 | +# ----------------------------------------------------------------------------- exercices | |
| 111 | + | |
| 112 | +def exercices(d: dict, base: dict) -> dict: | |
| 113 | + r = d["hyp"]["taux_actualisation"] | |
| 114 | + e = ecart_par_logement(d["etat"]) | |
| 115 | + l103 = e.loc[e["logement"] == 103].iloc[0] | |
| 116 | + ex3 = {"ecart_mensuel": float(l103["ecart_mensuel"]), "ecart_annuel": float(l103["ecart_annuel"]), | |
| 117 | + "va_5ans": valeur_bail_locataire(float(l103["ecart_annuel"]), 5, r), "va_10ans": valeur_bail_locataire(float(l103["ecart_annuel"]), 10, r), | |
| 118 | + "va_perpetuite": float(l103["ecart_annuel"]) / r} | |
| 119 | + # ex 4 : usufruit 15 ans sur un immeuble de 600 000 $ ; revenu net 30 000 $ croissant à 1 %/an ; r = 6 % (V = R_1/(r−g) = 600 000 $) | |
| 120 | + ex4 = usufruit_nue_propriete(30000.0, 0.01, 0.06, 15) | |
| 121 | + # ex 5 : valeur au rôle fictive du cas 1, 468 000 $ au 2024-07-01 ; indice +4,43 %/an ; date d'évaluation 2026-09-01 (26 mois) | |
| 122 | + ex5 = {"role": 468000.0, "mois": 26, "indice": 1.0443 ** (26 / 12), "valeur_indexee": 468000.0 * 1.0443 ** (26 / 12)} | |
| 123 | + # ex 6 : part des locataires selon la rotation (taux de base) — repris de la sensibilité | |
| 124 | + # ex 7 : hypothèse extraordinaire : décontamination 150 000 $ + stigmate 5 % sur la valeur DCF du locateur | |
| 125 | + v = base["V_locateur"] | |
| 126 | + ex7 = {"V_hyp": v, "couts": 150000.0, "stigmate": 0.05, "V_sans_hyp": (v - 150000.0) * 0.95, "incidence": v - (v - 150000.0) * 0.95} | |
| 127 | + return {"ex3": ex3, "ex4": ex4, "ex5": ex5, "ex7": ex7} | |
| 128 | + | |
| 129 | + | |
| 130 | +# ----------------------------------------------------------------------------- principal | |
| 131 | + | |
| 132 | +def main(verbose: bool = True) -> dict: | |
| 133 | + d = cas3.charger() | |
| 134 | + rev = cas3.revenus(d["etat"], d["hyp"]) | |
| 135 | + e = ecart_par_logement(d["etat"]) | |
| 136 | + base = decomposition(d) | |
| 137 | + cap = capitalisation_naive(base, cas3.taux_extraits(d["comps"])["mediane"]) | |
| 138 | + hyp = d["hyp"]; r = hyp["taux_actualisation"]; g = hyp["croissance_loyers_marche"]; rho = hyp["rotation_annuelle"]; v = hyp["taux_inoccupation_pertes"] | |
| 139 | + ecart_net1 = rev["ecart_marche"] * (1 - v) | |
| 140 | + ferme = {"ecart_net_annee1": ecart_net1, | |
| 141 | + "perpetuite_rotation": va_ecart_rotation(ecart_net1, r, g, rho), | |
| 142 | + "perpetuite_sans_rotation": va_ecart_rotation(ecart_net1, r, g, 0.0), | |
| 143 | + "horizon10_rotation": va_ecart_rotation(ecart_net1, r, g, rho, T=10), | |
| 144 | + "annuite_5ans": valeur_bail_locataire(ecart_net1, 5, r), "annuite_10ans": valeur_bail_locataire(ecart_net1, 10, r)} | |
| 145 | + sens = sensibilite(d) | |
| 146 | + ex = exercices(d, base) | |
| 147 | + res = {"d": d, "revenus": rev, "ecarts": e, "base": base, "cap": cap, "ferme": ferme, "sensibilite": sens, "exercices": ex} | |
| 148 | + if verbose: | |
| 149 | + pd.set_option("display.width", 200); pd.set_option("display.max_columns", 20) | |
| 150 | + print("=== Cas 3 — écart contractuel / marché (données synthétiques) ===") | |
| 151 | + print(f"RBP contractuel {rev['rbp_contrat']:,.0f} $ ; RBP au marché {rev['rbp_marche']:,.0f} $ ; écart {rev['ecart_marche']:,.0f} $/an " | |
| 152 | + f"({rev['ecart_marche']/rev['rbp_marche']*100:.1f} % du marché) ; écart net de vacance (3,9 %) {ecart_net1:,.0f} $") | |
| 153 | + print(e.to_string(index=False)) | |
| 154 | + print(f"ratio contrat/marché : moyenne {e['ratio_contrat_marche'].mean():.3f}, min {e['ratio_contrat_marche'].min():.3f} (log. {int(e.loc[e['ratio_contrat_marche'].idxmin(),'logement'])}), max {e['ratio_contrat_marche'].max():.3f}") | |
| 155 | + print("\n=== Décomposition DCF (hypothèses du cas 3 : r = 7,25 %, rotation 15 %, g_c 3,1 %, g_m 3,5 %, R_T 5 %, c_v 2,5 %) ===") | |
| 156 | + for k in ["rne1_contrat", "rne1_marche", "rbe_contrat", "rbe_marche", "va_flux_contrat", "va_flux_marche", "va_vt_contrat", "va_vt_marche", "V_locateur", "V_pleine", "V_locataires"]: | |
| 157 | + print(f"{k:18s} {base[k]:>14,.2f} $") | |
| 158 | + print(f"part des locataires : {base['part_locataires']*100:.2f} %") | |
| 159 | + print("\nProjection contractuelle vs marché (RBP) :") | |
| 160 | + pc, pm = base["proj_contrat"], base["proj_marche"] | |
| 161 | + print(pd.DataFrame({"annee": pc["annee"], "rbp_contrat": pc["rbp"], "rbp_marche_scen": pm["rbp"], "ecart_rbp": pm["rbp"] - pc["rbp"], | |
| 162 | + "rne_contrat": pc["rne"], "rne_marche": pm["rne"], "ecart_rne": pm["rne"] - pc["rne"]}).round(0).to_string(index=False)) | |
| 163 | + print("\n=== Capitalisation naïve de l'écart (taux médian extrait) ===") | |
| 164 | + print({k: round(x, 0) for k, x in cap.items()}) | |
| 165 | + print("\n=== Formules fermées (écart net année 1, g = 3,5 %) ===") | |
| 166 | + print({k: round(x, 0) for k, x in ferme.items()}) | |
| 167 | + print("\n=== Sensibilité rotation × taux (V_locataires et part) ===") | |
| 168 | + print(sens.assign(logements_an=(sens["rotation"] * 12).round(0), V_locataires=sens["V_locataires"].round(0), part=(sens["part"] * 100).round(2), V_locateur=sens["V_locateur"].round(0), V_pleine=sens["V_pleine"].round(0)).to_string(index=False)) | |
| 169 | + print("\n=== Exercices ===") | |
| 170 | + for k, x in ex.items(): | |
| 171 | + print(k, {kk: (round(vv, 4) if isinstance(vv, float) else vv) for kk, vv in x.items()}) | |
| 172 | + return res | |
| 173 | + | |
| 174 | + | |
| 175 | +if __name__ == "__main__": | |
| 176 | + main() | |
added
analysis/python/cases/ch03_cycles.py
+513 −0
@@ -0,0 +1,513 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Chapitre 3 — Offre, demande et cycles : calculs de l'exemple intégral et des tableaux. | |
| 3 | + | |
| 4 | +Trois blocs : | |
| 5 | +1. Modèle des quatre quadrants (DiPasquale–Wheaton) : équilibre stationnaire en forme fermée, statique comparative | |
| 6 | + (taux de capitalisation, demande, coût de construction). PARAMÈTRES PÉDAGOGIQUES (marché type, données synthétiques). | |
| 7 | +2. Dynamique stock-flux trimestrielle avec délai de livraison et inoccupation naturelle : simulation d'un cycle | |
| 8 | + après un choc de demande, racines caractéristiques du modèle linéaire (condition d'oscillation), sensibilité. | |
| 9 | +3. Données RÉELLES lues dans data/raw : mises en chantier au Québec (Statistique Canada 34-10-0135-01 et 34-10-0143-01), | |
| 10 | + taux de la Banque du Canada (Valet : V39079, BD.CDN.5YR.DQ.YLD, V80691335, groupe A4), IPC Québec (18-10-0004-01), | |
| 11 | + IPLN Québec (18-10-0205-01) ; coût d'usage du logement (Poterba) et ratio prix/loyer implicite ; mois d'inventaire | |
| 12 | + (chiffres APCIQ T3 2026 cités dans sources/notes-E.md). | |
| 13 | + | |
| 14 | +Exécuter depuis la racine : python3 analysis/python/cases/ch03_cycles.py (imprime tous les chiffres cités au chapitre 3). | |
| 15 | +Testé par analysis/python/tests/test_ch03.py. | |
| 16 | +""" | |
| 17 | +from __future__ import annotations | |
| 18 | +import csv, math, pathlib, sys | |
| 19 | +from dataclasses import dataclass, replace | |
| 20 | +import numpy as np | |
| 21 | +import pandas as pd | |
| 22 | + | |
| 23 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 24 | +sys.path.insert(0, str(ROOT / "analysis/python")) | |
| 25 | +from traite import finance as fin # noqa: E402 (P = R / i : capitalisation directe de la bibliothèque) | |
| 26 | + | |
| 27 | +RAW = ROOT / "data/raw" | |
| 28 | +GRAINE = 20261031 | |
| 29 | + | |
| 30 | +# ============================================================================================================= | |
| 31 | +# 1. Quatre quadrants — paramètres pédagogiques d'un marché type (SYNTHÉTIQUE) | |
| 32 | +# ============================================================================================================= | |
| 33 | + | |
| 34 | +@dataclass(frozen=True) | |
| 35 | +class Parametres4Q: | |
| 36 | + """Marché type : S en logements, R en $/logement/an, P en $/logement, C en logements/an. | |
| 37 | + Calibration pédagogique (ordres de grandeur) : loyer 1 200 $/mois ≈ loyer moyen SCHL RMR de Québec oct. 2025 | |
| 38 | + (1 227 $, tous logements) ; taux de capitalisation 5 % ≈ fourchette CBRE Québec T3 2026 (4,25-6,00 %) ; | |
| 39 | + élasticité-prix de la demande d'espace −0,5 et taux de retrait δ = 1 %/an : hypothèses déclarées (non estimées).""" | |
| 40 | + S0: float = 100_000.0 # stock initial (logements) | |
| 41 | + R0: float = 14_400.0 # loyer annuel d'équilibre initial ($/logement/an) = 1 200 $/mois | |
| 42 | + eta: float = -0.5 # élasticité-prix de la demande d'espace au point initial | |
| 43 | + i: float = 0.05 # taux de capitalisation (marché des actifs) | |
| 44 | + delta: float = 0.01 # taux de retrait du stock (démolitions, conversions), par an | |
| 45 | + c0: float = 240_000.0 # seuil de coût : prix en deçà duquel rien ne se construit ($/logement) | |
| 46 | + | |
| 47 | + @property | |
| 48 | + def b(self) -> float: | |
| 49 | + """Pente de la demande D = a − bR ; b = −η S0 / R0.""" | |
| 50 | + return -self.eta * self.S0 / self.R0 | |
| 51 | + | |
| 52 | + @property | |
| 53 | + def a(self) -> float: | |
| 54 | + return self.S0 + self.b * self.R0 | |
| 55 | + | |
| 56 | + @property | |
| 57 | + def C0(self) -> float: | |
| 58 | + return self.delta * self.S0 | |
| 59 | + | |
| 60 | + @property | |
| 61 | + def P0(self) -> float: | |
| 62 | + return fin.capitalisation_directe(self.R0, self.i) | |
| 63 | + | |
| 64 | + @property | |
| 65 | + def c1(self) -> float: | |
| 66 | + """Pente de l'offre de construction C = (P − c0)/c1, telle que C0 = δ S0 au prix initial.""" | |
| 67 | + return (self.P0 - self.c0) / self.C0 | |
| 68 | + | |
| 69 | + | |
| 70 | +def equilibre_4q(p: Parametres4Q, a: float | None = None, i: float | None = None, c0: float | None = None) -> dict: | |
| 71 | + """Équilibre stationnaire des quatre quadrants en forme fermée : | |
| 72 | + S* = (a − b i c0) / (1 + b i c1 δ) ; R* = (a − S*)/b ; P* = R*/i ; C* = δ S*.""" | |
| 73 | + a = p.a if a is None else a | |
| 74 | + i = p.i if i is None else i | |
| 75 | + c0 = p.c0 if c0 is None else c0 | |
| 76 | + b, c1, d = p.b, p.c1, p.delta | |
| 77 | + S = (a - b * i * c0) / (1.0 + b * i * c1 * d) | |
| 78 | + R = (a - S) / b | |
| 79 | + P = fin.capitalisation_directe(R, i) | |
| 80 | + C = d * S | |
| 81 | + assert abs(C - (P - c0) / c1) < 1e-6 * max(1.0, C) | |
| 82 | + return {"S": S, "R": R, "P": P, "C": C, "a": a, "i": i, "c0": c0} | |
| 83 | + | |
| 84 | + | |
| 85 | +def equilibre_4q_iteratif(p: Parametres4Q, a: float | None = None, i: float | None = None, c0: float | None = None, | |
| 86 | + n_iter: int = 2000) -> dict: | |
| 87 | + """Même équilibre par itération de point fixe S ← C(P(R(S)))/δ (vérification numérique de la forme fermée).""" | |
| 88 | + a = p.a if a is None else a | |
| 89 | + i = p.i if i is None else i | |
| 90 | + c0 = p.c0 if c0 is None else c0 | |
| 91 | + S = p.S0 | |
| 92 | + for _ in range(n_iter): | |
| 93 | + R = (a - S) / p.b | |
| 94 | + P = fin.capitalisation_directe(R, i) | |
| 95 | + C = max(0.0, (P - c0) / p.c1) | |
| 96 | + S = 0.5 * S + 0.5 * C / p.delta # relaxation pour garantir la convergence | |
| 97 | + R = (a - S) / p.b | |
| 98 | + return {"S": S, "R": R, "P": fin.capitalisation_directe(R, i), "C": p.delta * S} | |
| 99 | + | |
| 100 | + | |
| 101 | +def court_terme_4q(p: Parametres4Q, a: float | None = None, i: float | None = None) -> dict: | |
| 102 | + """Court terme : stock fixe à S0 ; le loyer et le prix absorbent le choc.""" | |
| 103 | + a = p.a if a is None else a | |
| 104 | + i = p.i if i is None else i | |
| 105 | + R = (a - p.S0) / p.b | |
| 106 | + P = fin.capitalisation_directe(R, i) | |
| 107 | + return {"S": p.S0, "R": R, "P": P, "C": max(0.0, (P - p.c0) / p.c1)} | |
| 108 | + | |
| 109 | + | |
| 110 | +def statique_comparative(p: Parametres4Q) -> pd.DataFrame: | |
| 111 | + """Trois chocs : baisse du taux de capitalisation à 4 %, hausse de la demande de 10 %, hausse du coût de 10 %.""" | |
| 112 | + base = equilibre_4q(p) | |
| 113 | + chocs = { | |
| 114 | + "Référence": dict(), | |
| 115 | + "Taux i : 5 % → 4 %": dict(i=0.04), | |
| 116 | + "Demande a : +10 %": dict(a=1.10 * p.a), | |
| 117 | + "Coût c0 : +10 %": dict(c0=1.10 * p.c0), | |
| 118 | + } | |
| 119 | + rows = [] | |
| 120 | + for nom, kw in chocs.items(): | |
| 121 | + lt = equilibre_4q(p, **kw) | |
| 122 | + ct = court_terme_4q(p, a=kw.get("a"), i=kw.get("i")) if "c0" not in kw else {"R": base["R"], "P": base["P"]} | |
| 123 | + rows.append({"choc": nom, "R_ct": ct["R"], "P_ct": ct["P"], "S_lt": lt["S"], "R_lt": lt["R"], "P_lt": lt["P"], "C_lt": lt["C"], | |
| 124 | + "dS_pct": 100 * (lt["S"] / base["S"] - 1), "dR_ct_pct": 100 * (ct["R"] / base["R"] - 1), | |
| 125 | + "dR_lt_pct": 100 * (lt["R"] / base["R"] - 1), "dP_ct_pct": 100 * (ct["P"] / base["P"] - 1), | |
| 126 | + "dP_lt_pct": 100 * (lt["P"] / base["P"] - 1)}) | |
| 127 | + return pd.DataFrame(rows).set_index("choc") | |
| 128 | + | |
| 129 | + | |
| 130 | +def elasticites_4q(p: Parametres4Q) -> dict: | |
| 131 | + """Élasticités au point initial : construction/prix (court terme) et stock stationnaire/demande (long terme).""" | |
| 132 | + eps_C = (1.0 / p.c1) * (p.P0 / p.C0) # (dC/dP)(P/C) | |
| 133 | + kappa = 1.0 / (p.b * p.i * p.c1) # réactivité de l'offre à l'écart de stock (par an) | |
| 134 | + base = equilibre_4q(p) | |
| 135 | + plus = equilibre_4q(p, a=1.01 * p.a) | |
| 136 | + eps_S_a = (plus["S"] / base["S"] - 1) / 0.01 # élasticité du stock stationnaire à l'ordonnée de demande | |
| 137 | + return {"eps_C_P": eps_C, "kappa": kappa, "eps_S_a": eps_S_a, "eta": p.eta} | |
| 138 | + | |
| 139 | + | |
| 140 | +# ============================================================================================================= | |
| 141 | +# 2. Dynamique stock-flux (SYNTHÉTIQUE) — simulation trimestrielle et racines caractéristiques | |
| 142 | +# ============================================================================================================= | |
| 143 | + | |
| 144 | +@dataclass(frozen=True) | |
| 145 | +class ParametresCycle: | |
| 146 | + p4q: Parametres4Q = Parametres4Q() | |
| 147 | + v_nat: float = 0.03 # taux d'inoccupation naturel (SCHL RMR Québec oct. 2025 : 2,4 % ; Montréal 2,9 %) | |
| 148 | + lam: float = 0.5 # vitesse d'ajustement du loyer : ΔR/R = −λ (v − v*) par trimestre | |
| 149 | + L: int = 6 # délai de livraison (trimestres) entre la mise en chantier et l'ajout au stock | |
| 150 | + T: int = 80 # horizon (trimestres) | |
| 151 | + t_choc: int = 8 # trimestre du choc | |
| 152 | + choc_demande: float = 0.10 # hausse permanente de l'ordonnée de demande (+10 %) | |
| 153 | + mult_c1: float = 1.0 # multiplicateur de la pente d'offre c1 (1 = référence ; >1 = offre moins élastique) | |
| 154 | + | |
| 155 | + | |
| 156 | +def simuler_cycle(pc: ParametresCycle) -> pd.DataFrame: | |
| 157 | + """Trimestriel. Demande occupée D = a − bR ; inoccupation v = 1 − D/S ; loyer R_{t+1} = R_t[1 − λ(v_t − v*)] ; | |
| 158 | + prix P = R/i ; mises en chantier C = max(0, (P − c0)/c1)/4 ; stock S_{t+1} = (1 − δ/4) S_t + C_{t−L}. | |
| 159 | + L'état initial est stationnaire avec v = v* (l'ordonnée a est ajustée pour que D0 = (1 − v*) S0).""" | |
| 160 | + p = pc.p4q | |
| 161 | + c1 = p.c1 * pc.mult_c1 | |
| 162 | + b = p.b | |
| 163 | + a0 = (1.0 - pc.v_nat) * p.S0 + b * p.R0 | |
| 164 | + S, R = p.S0, p.R0 | |
| 165 | + starts = [p.C0 / 4.0] * (pc.L + 1) # chantiers en cours au régime stationnaire | |
| 166 | + rows = [] | |
| 167 | + for t in range(pc.T + 1): | |
| 168 | + a = a0 * (1.0 + pc.choc_demande) if t >= pc.t_choc else a0 | |
| 169 | + D = min(S, a - b * R) | |
| 170 | + v = 1.0 - D / S | |
| 171 | + P = fin.capitalisation_directe(R, p.i) | |
| 172 | + C = max(0.0, (P - p.c0) / c1) / 4.0 | |
| 173 | + rows.append({"t": t, "annee": t / 4.0, "S": S, "D": D, "v": v, "R": R, "P": P, "C": C, "a": a}) | |
| 174 | + starts.append(C) | |
| 175 | + livraison = starts[-(pc.L + 1)] | |
| 176 | + S = (1.0 - p.delta / 4.0) * S + livraison | |
| 177 | + R = R * (1.0 - pc.lam * (v - pc.v_nat)) | |
| 178 | + df = pd.DataFrame(rows) | |
| 179 | + df["R_mois"] = df["R"] / 12.0 | |
| 180 | + df["C_annuel"] = df["C"] * 4.0 | |
| 181 | + return df | |
| 182 | + | |
| 183 | + | |
| 184 | +def etat_stationnaire_cycle(pc: ParametresCycle, apres_choc: bool = True) -> dict: | |
| 185 | + """État stationnaire du modèle dynamique (avec inoccupation naturelle) : v = v*, D = (1−v*)S, C = δS/4.""" | |
| 186 | + p = pc.p4q | |
| 187 | + b = p.b | |
| 188 | + a0 = (1.0 - pc.v_nat) * p.S0 + b * p.R0 | |
| 189 | + a = a0 * (1.0 + pc.choc_demande) if apres_choc else a0 | |
| 190 | + # (1−v*) S = a − bR ; R = i P ; P = c0 + c1 δ S → (1−v*) S = a − b i (c0 + c1 δ S) | |
| 191 | + c1 = p.c1 * pc.mult_c1 | |
| 192 | + S = (a - b * p.i * p.c0) / ((1.0 - pc.v_nat) + b * p.i * c1 * p.delta) | |
| 193 | + P = p.c0 + c1 * p.delta * S | |
| 194 | + R = p.i * P | |
| 195 | + return {"S": S, "R": R, "P": P, "C": p.delta * S, "v": pc.v_nat} | |
| 196 | + | |
| 197 | + | |
| 198 | +def resume_cycle(df: pd.DataFrame, pc: ParametresCycle) -> dict: | |
| 199 | + """Mesures du cycle : pointe et creux de loyer (écart au stationnaire final), pointe d'inoccupation, période.""" | |
| 200 | + ss = etat_stationnaire_cycle(pc) | |
| 201 | + apres = df[df["t"] >= pc.t_choc] | |
| 202 | + R_rel = apres["R"] / ss["R"] - 1.0 | |
| 203 | + i_max = int(R_rel.idxmax()) | |
| 204 | + # creux après la première pointe | |
| 205 | + post = R_rel.loc[i_max:] | |
| 206 | + i_min = int(post.idxmin()) | |
| 207 | + # seconde pointe (pour la période) : maximum après le creux | |
| 208 | + post2 = R_rel.loc[i_min:] | |
| 209 | + i_max2 = int(post2.idxmax()) if len(post2) > 1 else i_max | |
| 210 | + periode = (i_max2 - i_max) if i_max2 > i_max and post2.max() > 0 else float("nan") | |
| 211 | + return {"R_pointe_pct": 100 * R_rel.max(), "t_pointe": df.loc[i_max, "t"] - pc.t_choc, "R_creux_pct": 100 * post.min(), | |
| 212 | + "t_creux": df.loc[i_min, "t"] - pc.t_choc, "v_max_pct": 100 * apres["v"].max(), "v_min_pct": 100 * apres["v"].min(), | |
| 213 | + "C_max_annuel": apres["C_annuel"].max(), "periode_trim": periode, | |
| 214 | + "S_final_ecart_pct": 100 * (df["S"].iloc[-1] / ss["S"] - 1), "v_final_pct": 100 * df["v"].iloc[-1], | |
| 215 | + "amortissement": (post2.max() / R_rel.max()) if periode == periode else float("nan")} | |
| 216 | + | |
| 217 | + | |
| 218 | +def sensibilite_cycle(pc: ParametresCycle, delais=(2, 4, 6, 8, 12), mult_c1=(0.5, 1.0, 2.0), lams=(0.25, 0.5, 1.0)) -> pd.DataFrame: | |
| 219 | + rows = [] | |
| 220 | + for L in delais: | |
| 221 | + for m in mult_c1: | |
| 222 | + for lam in lams: | |
| 223 | + r = resume_cycle(simuler_cycle(replace(pc, L=L, mult_c1=m, lam=lam)), replace(pc, L=L, mult_c1=m, lam=lam)) | |
| 224 | + rows.append({"L": L, "mult_c1": m, "lam": lam, **r}) | |
| 225 | + return pd.DataFrame(rows) | |
| 226 | + | |
| 227 | + | |
| 228 | +def racines_caracteristiques(kappa: float, delta: float, L: int = 1) -> dict: | |
| 229 | + """Modèle linéaire annuel S_{t+1} = (1−δ) S_t + κ (a − S_{t−L}) + cte, κ = 1/(b i c1). | |
| 230 | + Polynôme : r^{L+1} − (1−δ) r^L + κ = 0. Retourne le module maximal, la nature des racines et la période.""" | |
| 231 | + coeffs = np.zeros(L + 2) | |
| 232 | + coeffs[0] = 1.0 | |
| 233 | + coeffs[1] = -(1.0 - delta) | |
| 234 | + coeffs[-1] = kappa | |
| 235 | + r = np.roots(coeffs) | |
| 236 | + dom = r[np.argmax(np.abs(r))] | |
| 237 | + complexe = bool(abs(dom.imag) > 1e-9) | |
| 238 | + periode = (2 * math.pi / abs(np.angle(dom))) if complexe else float("inf") | |
| 239 | + return {"racines": r, "module": float(abs(dom)), "oscille": complexe, "periode": periode, "stable": bool(abs(dom) < 1.0)} | |
| 240 | + | |
| 241 | + | |
| 242 | +def condition_oscillation_L1(kappa: float, delta: float) -> dict: | |
| 243 | + """Cas L = 1 en forme fermée : r² − (1−δ) r + κ = 0 ; racines complexes ssi κ > (1−δ)²/4 ; module √κ ; | |
| 244 | + période 2π / arccos((1−δ)/(2√κ)).""" | |
| 245 | + seuil = (1.0 - delta) ** 2 / 4.0 | |
| 246 | + if kappa > seuil: | |
| 247 | + return {"seuil": seuil, "oscille": True, "module": math.sqrt(kappa), "periode": 2 * math.pi / math.acos((1 - delta) / (2 * math.sqrt(kappa))), | |
| 248 | + "stable": kappa < 1.0} | |
| 249 | + disc = math.sqrt((1 - delta) ** 2 - 4 * kappa) | |
| 250 | + r1, r2 = ((1 - delta) + disc) / 2, ((1 - delta) - disc) / 2 | |
| 251 | + return {"seuil": seuil, "oscille": False, "module": max(abs(r1), abs(r2)), "periode": float("inf"), "stable": max(abs(r1), abs(r2)) < 1.0} | |
| 252 | + | |
| 253 | + | |
| 254 | +# ============================================================================================================= | |
| 255 | +# 3. Données RÉELLES | |
| 256 | +# ============================================================================================================= | |
| 257 | + | |
| 258 | +def _lire_statcan(fichier: pathlib.Path, filtre) -> list[dict]: | |
| 259 | + with open(fichier, encoding="utf-8-sig") as f: | |
| 260 | + return [r for r in csv.DictReader(f, delimiter=";") if filtre(r) and r["VALEUR"]] | |
| 261 | + | |
| 262 | + | |
| 263 | +def mises_en_chantier_quebec() -> pd.DataFrame: | |
| 264 | + """34-10-0135-01 : Québec, toutes régions, trimestriel, non désaisonnalisé, mises en chantier par type d'unité. | |
| 265 | + Périodes étiquetées par le premier mois du trimestre (1948-01 = T1 1948).""" | |
| 266 | + rows = _lire_statcan(RAW / "statcan_34100135/34100135.csv", | |
| 267 | + lambda r: r["GÉO"] == "Québec" and r["Désaisonnalisation"] == "Non désaisonnalisés" | |
| 268 | + and r["Estimations de logement"] == "Logements mis en chantier") | |
| 269 | + df = pd.DataFrame([{"periode": r["PÉRIODE DE RÉFÉRENCE"], "type": r["Type d'unité"], "unites": float(r["VALEUR"]), "vecteur": r["VECTEUR"]} for r in rows]) | |
| 270 | + df["annee"] = df["periode"].str[:4].astype(int) | |
| 271 | + df["trimestre"] = df["periode"].str[5:7].map({"01": 1, "04": 2, "07": 3, "10": 4}) | |
| 272 | + df["date"] = pd.PeriodIndex(df["annee"].astype(str) + "Q" + df["trimestre"].astype(str), freq="Q").to_timestamp() | |
| 273 | + return df.sort_values(["type", "date"]).reset_index(drop=True) | |
| 274 | + | |
| 275 | + | |
| 276 | +def mises_en_chantier_annuelles(df: pd.DataFrame | None = None) -> pd.DataFrame: | |
| 277 | + """Sommes annuelles (années complètes à 4 trimestres) par type ; part des maisons individuelles et des collectifs.""" | |
| 278 | + df = mises_en_chantier_quebec() if df is None else df | |
| 279 | + piv = df.pivot_table(index="annee", columns="type", values="unites", aggfunc="sum") | |
| 280 | + n_trim = df[df["type"] == "Total d'unités"].groupby("annee")["trimestre"].count() | |
| 281 | + piv["trimestres"] = n_trim | |
| 282 | + piv = piv.rename(columns={"Total d'unités": "total", "Maisons individuelles": "individuelles", "Logements collectifs": "collectifs", | |
| 283 | + "Appartements et autres logements": "appartements", "Logements jumelés": "jumeles", "Logements en bande": "bande"}) | |
| 284 | + piv["part_individuelles"] = piv["individuelles"] / piv["total"] | |
| 285 | + piv["part_collectifs"] = piv["collectifs"] / piv["total"] | |
| 286 | + piv["part_appartements"] = piv["appartements"] / piv["total"] | |
| 287 | + return piv | |
| 288 | + | |
| 289 | + | |
| 290 | +def mises_en_chantier_montreal_annuelles() -> pd.Series: | |
| 291 | + """34-10-0143-01 : RMR de Montréal, mensuel, total d'unités, sommes annuelles.""" | |
| 292 | + rows = _lire_statcan(RAW / "statcan_34100143/34100143.csv", | |
| 293 | + lambda r: r["GÉO"] == "Montréal, Québec" and r["Estimations de logements"] == "Logements mis en chantier" | |
| 294 | + and r["Type d'unité"] == "Total d'unités") | |
| 295 | + s = pd.Series({r["PÉRIODE DE RÉFÉRENCE"]: float(r["VALEUR"]) for r in rows}) | |
| 296 | + s.index = pd.to_datetime(s.index) | |
| 297 | + ann = s.groupby(s.index.year).sum() | |
| 298 | + mois = s.groupby(s.index.year).count() | |
| 299 | + return pd.DataFrame({"total": ann, "mois": mois}) | |
| 300 | + | |
| 301 | + | |
| 302 | +def _valet(nom: str) -> pd.Series: | |
| 303 | + rows = list(csv.reader(open(RAW / "bdc_valet" / f"{nom}.csv", encoding="utf-8-sig"))) | |
| 304 | + i = next(k for k, r in enumerate(rows) if r and r[0] == "date") | |
| 305 | + s = pd.Series({r[0]: float(r[1]) for r in rows[i + 1:] if len(r) > 1 and r[1]}) | |
| 306 | + s.index = pd.to_datetime(s.index) | |
| 307 | + return s.sort_index() | |
| 308 | + | |
| 309 | + | |
| 310 | +def taux_bdc_mensuels() -> pd.DataFrame: | |
| 311 | + """Moyennes mensuelles : taux hypothécaire affiché 5 ans (V80691335), obligation GdC 5 ans et 10 ans, taux directeur (V39079, depuis 2009 dans Valet).""" | |
| 312 | + out = pd.DataFrame({ | |
| 313 | + "hyp5": _valet("V80691335").resample("MS").mean(), | |
| 314 | + "gdc5": _valet("BD.CDN.5YR.DQ.YLD").resample("MS").mean(), | |
| 315 | + "gdc10": _valet("BD.CDN.10YR.DQ.YLD").resample("MS").mean(), | |
| 316 | + "directeur": _valet("V39079").resample("MS").mean(), | |
| 317 | + }) | |
| 318 | + return out | |
| 319 | + | |
| 320 | + | |
| 321 | +def changements_taux_directeur(depuis: str = "2020-01-01") -> pd.DataFrame: | |
| 322 | + """Dates d'effet (Valet, lendemain de l'annonce) et niveaux du taux cible du financement à un jour (V39079).""" | |
| 323 | + s = _valet("V39079") | |
| 324 | + ch = s[s.diff().fillna(0) != 0] | |
| 325 | + ch = ch[ch.index >= depuis] | |
| 326 | + prev = s.shift(1).reindex(ch.index) | |
| 327 | + return pd.DataFrame({"avant": prev.values, "apres": ch.values, "variation_pb": ((ch - prev) * 100).round().astype(int).values}, index=ch.index) | |
| 328 | + | |
| 329 | + | |
| 330 | +def taux_effectifs_a4(mois: str = "2026-08-01") -> dict: | |
| 331 | + """Groupe A4 (fonds avancés) : V122667783 = assuré, fixe 5 ans et plus ; V122667789 = non assuré, fixe 5 ans et plus | |
| 332 | + (correspondance d'après les métadonnées du fichier ; valeurs d'août 2026 citées dans notes-C/E : 4,12 % et 4,58 %).""" | |
| 333 | + rows = list(csv.reader(open(RAW / "bdc_valet/A4_RATES_MORTGAGES.csv", encoding="utf-8-sig"))) | |
| 334 | + i = next(k for k, r in enumerate(rows) if r and r[0] == "date") | |
| 335 | + hdr = rows[i] | |
| 336 | + rec = next(r for r in rows[i + 1:] if r and r[0] == mois) | |
| 337 | + d = dict(zip(hdr, rec)) | |
| 338 | + # libellés des séries (bloc SERIES du fichier) | |
| 339 | + labels = {r[0]: r[1] for r in rows[:i] if len(r) >= 2 and r[0].startswith("V1226677")} | |
| 340 | + assure = [k for k, v in labels.items() if "Insured" in v and "Uninsured" not in v and "5 years" in v and "Funds advanced" in v] | |
| 341 | + non_assure = [k for k, v in labels.items() if "Uninsured" in v and "5 years" in v and "Funds advanced" in v] | |
| 342 | + return {"mois": mois, "assure_fixe_5ans_plus": float(d[assure[0]]) if assure else float("nan"), | |
| 343 | + "non_assure_fixe_5ans_plus": float(d[non_assure[0]]) if non_assure else float("nan"), | |
| 344 | + "labels_assure": [labels[k] for k in assure], "labels_non_assure": [labels[k] for k in non_assure]} | |
| 345 | + | |
| 346 | + | |
| 347 | +def ipc_quebec(produit: str = "Ensemble") -> pd.Series: | |
| 348 | + """18-10-0004-01, Québec, non désaisonnalisé, 2002 = 100 (Ensemble : v41691783 ; Loyer : v41691818).""" | |
| 349 | + rows = _lire_statcan(RAW / "statcan_18100004/18100004.csv", lambda r: r["GÉO"] == "Québec" and r["Produits et groupes de produits"] == produit) | |
| 350 | + s = pd.Series({r["PÉRIODE DE RÉFÉRENCE"] + "-01": float(r["VALEUR"]) for r in rows}) | |
| 351 | + s.index = pd.to_datetime(s.index) | |
| 352 | + return s.sort_index() | |
| 353 | + | |
| 354 | + | |
| 355 | +def ipln_quebec(geo: str = "Québec", composante: str = "Total (maison et terrain)") -> pd.Series: | |
| 356 | + rows = _lire_statcan(RAW / "statcan_18100205/18100205.csv", lambda r: r["GÉO"] == geo and r["Indices des prix des logements neufs"] == composante) | |
| 357 | + s = pd.Series({r["PÉRIODE DE RÉFÉRENCE"] + "-01": float(r["VALEUR"]) for r in rows}) | |
| 358 | + s.index = pd.to_datetime(s.index) | |
| 359 | + return s.sort_index() | |
| 360 | + | |
| 361 | + | |
| 362 | +# --- coût d'usage ------------------------------------------------------------------------------------------- | |
| 363 | + | |
| 364 | +TAUX_TAXE_QUEBEC_2026 = 0.007464 + 0.0007899 # Ville de Québec 1-5 log. (0,7464 $/100 $) + taxe scolaire 2026-2027 (0,07899 $/100 $) | |
| 365 | +DELTA_ENTRETIEN_RISQUE = 0.025 # dépréciation + entretien + prime de risque : HYPOTHÈSE PÉDAGOGIQUE déclarée | |
| 366 | + | |
| 367 | + | |
| 368 | +def cout_usage(P: float, i: float, tau: float, delta: float, g: float) -> float: | |
| 369 | + """u = P (i + τ + δ − g) : coût annuel d'occupation d'un logement de prix P (Poterba 1984).""" | |
| 370 | + return P * (i + tau + delta - g) | |
| 371 | + | |
| 372 | + | |
| 373 | +def taux_cout_usage(i: float, tau: float, delta: float, g: float) -> float: | |
| 374 | + return i + tau + delta - g | |
| 375 | + | |
| 376 | + | |
| 377 | +def ratio_prix_loyer(i: float, tau: float, delta: float, g: float) -> float: | |
| 378 | + """À l'équilibre R = u → P/R = 1/(i + τ + δ − g) (multiple du loyer annuel).""" | |
| 379 | + u = taux_cout_usage(i, tau, delta, g) | |
| 380 | + if u <= 0: | |
| 381 | + return float("inf") | |
| 382 | + return 1.0 / u | |
| 383 | + | |
| 384 | + | |
| 385 | +def serie_cout_usage(tau: float = TAUX_TAXE_QUEBEC_2026, delta: float = DELTA_ENTRETIEN_RISQUE, debut: str = "2001-01-01") -> pd.DataFrame: | |
| 386 | + """Série mensuelle réelle : i = taux hypothécaire affiché 5 ans ; π = inflation IPC Québec sur 12 mois ; | |
| 387 | + g_ipc = π (anticipation = inflation courante) ; g_ipln = croissance annualisée de l'IPLN Québec sur 5 ans ; | |
| 388 | + u et P/R pour chaque hypothèse. τ et δ constants (déclarés).""" | |
| 389 | + bdc = taux_bdc_mensuels() | |
| 390 | + ipc = ipc_quebec() | |
| 391 | + pi = (ipc / ipc.shift(12) - 1.0) * 100 | |
| 392 | + ipln = ipln_quebec() | |
| 393 | + g_ipln = ((ipln / ipln.shift(60)) ** (1 / 5) - 1.0) * 100 | |
| 394 | + df = pd.DataFrame({"hyp5": bdc["hyp5"], "gdc5": bdc["gdc5"], "directeur": bdc["directeur"], "pi": pi, "g_ipln": g_ipln}).loc[debut:] | |
| 395 | + df = df.dropna(subset=["hyp5", "pi"]) | |
| 396 | + df["reel"] = df["hyp5"] - df["pi"] | |
| 397 | + df["u_ipc"] = df["hyp5"] + 100 * (tau + delta) - df["pi"] | |
| 398 | + df["u_ipln"] = df["hyp5"] + 100 * (tau + delta) - df["g_ipln"] | |
| 399 | + df["pr_ipc"] = 100.0 / df["u_ipc"] | |
| 400 | + df["pr_ipln"] = (100.0 / df["u_ipln"]).where(df["u_ipln"] > 0) | |
| 401 | + return df | |
| 402 | + | |
| 403 | + | |
| 404 | +DATES_TABLEAU_COUT_USAGE = ["2001-01-01", "2008-01-01", "2012-01-01", "2020-07-01", "2023-10-01", "2026-09-01"] | |
| 405 | + | |
| 406 | + | |
| 407 | +def exemple_cout_usage(prix: float = 463_500.0, i_affiche: float = 0.0619, i_effectif: float = 0.0458, pi: float = 0.031, | |
| 408 | + g_ipln: float = 0.013, tau: float = TAUX_TAXE_QUEBEC_2026, delta: float = DELTA_ENTRETIEN_RISQUE) -> dict: | |
| 409 | + """Exemple intégral : unifamiliale au prix médian APCIQ RMR de Québec T3 2026 (463 500 $) ; taux affiché 5 ans 6,19 % | |
| 410 | + (BdC 2026-10-28) ; taux effectif non assuré fixe ≥ 5 ans 4,58 % (A4, août 2026) ; inflation IPC Québec 3,1 % ; | |
| 411 | + IPLN Québec +1,3 % (sept. 2026). Semi-élasticité : d ln(P/R) / di = −1/u.""" | |
| 412 | + out = {} | |
| 413 | + for nom, i in (("affiche", i_affiche), ("effectif", i_effectif)): | |
| 414 | + for gnom, g in (("ipc", pi), ("ipln", g_ipln)): | |
| 415 | + u = taux_cout_usage(i, tau, delta, g) | |
| 416 | + out[f"u_{nom}_{gnom}"] = u | |
| 417 | + out[f"cout_annuel_{nom}_{gnom}"] = cout_usage(prix, i, tau, delta, g) | |
| 418 | + out[f"cout_mensuel_{nom}_{gnom}"] = cout_usage(prix, i, tau, delta, g) / 12 | |
| 419 | + out[f"pr_{nom}_{gnom}"] = ratio_prix_loyer(i, tau, delta, g) | |
| 420 | + # sensibilité : +100 pb sur le taux effectif, anticipation = IPC | |
| 421 | + u0 = taux_cout_usage(i_effectif, tau, delta, pi) | |
| 422 | + u1 = taux_cout_usage(i_effectif + 0.01, tau, delta, pi) | |
| 423 | + out["pr_effectif_ipc_plus100pb"] = 1 / u1 | |
| 424 | + out["var_pr_plus100pb_pct"] = 100 * ((1 / u1) / (1 / u0) - 1) | |
| 425 | + out["semi_elasticite"] = -1 / u0 | |
| 426 | + # loyer de référence SCHL (RMR Québec oct. 2025, appartements 3 ch. et plus) pour comparaison | |
| 427 | + out["loyer_schl_3ch_quebec"] = 1475.0 | |
| 428 | + out["loyer_schl_2ch_quebec"] = 1277.0 | |
| 429 | + out["prix_implicite_loyer_3ch_effectif_ipc"] = 1475.0 * 12 / u0 | |
| 430 | + return out | |
| 431 | + | |
| 432 | + | |
| 433 | +# --- indicateurs d'alerte (APCIQ T3 2026, chiffres cités dans sources/notes-E.md) -------------------------------- | |
| 434 | + | |
| 435 | +APCIQ_T3_2026 = { | |
| 436 | + # territoire : (ventes T3 2026, inscriptions en vigueur, médiane unifamiliale) | |
| 437 | + "Province": (20_935, 42_408, 500_000), | |
| 438 | + "RMR Montréal": (9_265, 20_350, 645_500), | |
| 439 | + "RMR Québec": (2_161, 2_204, 463_500), | |
| 440 | + "RMR Gatineau": (1_044, 2_256, 480_000), | |
| 441 | + "RMR Sherbrooke": (569, 825, 490_750), | |
| 442 | + "RMR Trois-Rivières": (372, 447, 395_000), | |
| 443 | + "RMR Saguenay": (371, 498, 365_500), | |
| 444 | +} | |
| 445 | + | |
| 446 | + | |
| 447 | +def mois_inventaire(inscriptions: float, ventes_trimestre: float) -> float: | |
| 448 | + """Mois d'inventaire = inscriptions en vigueur / ventes mensuelles moyennes du trimestre.""" | |
| 449 | + return inscriptions / (ventes_trimestre / 3.0) | |
| 450 | + | |
| 451 | + | |
| 452 | +def indicateurs_apciq() -> pd.DataFrame: | |
| 453 | + rows = [] | |
| 454 | + for terr, (v, insc, med) in APCIQ_T3_2026.items(): | |
| 455 | + rows.append({"territoire": terr, "ventes_t3": v, "inscriptions": insc, "ventes_mensuelles": v / 3.0, | |
| 456 | + "mois_inventaire": mois_inventaire(insc, v), "mediane_unifamiliale": med}) | |
| 457 | + return pd.DataFrame(rows).set_index("territoire") | |
| 458 | + | |
| 459 | + | |
| 460 | +# ============================================================================================================= | |
| 461 | +# Impression | |
| 462 | +# ============================================================================================================= | |
| 463 | + | |
| 464 | +def _f(x, d=0): | |
| 465 | + return f"{x:,.{d}f}".replace(",", " ") | |
| 466 | + | |
| 467 | + | |
| 468 | +def main() -> None: | |
| 469 | + p = Parametres4Q() | |
| 470 | + print("=== 1. Quatre quadrants (marché type, SYNTHÉTIQUE) ===") | |
| 471 | + print(f"b = {p.b:.4f} log./$ ; a = {_f(p.a)} ; P0 = {_f(p.P0)} $ ; C0 = {_f(p.C0)} ; c1 = {p.c1:.2f} $ par (log./an)") | |
| 472 | + eq = equilibre_4q(p); it = equilibre_4q_iteratif(p) | |
| 473 | + print(f"Équilibre fermé : S*={_f(eq['S'])} R*={_f(eq['R'])} P*={_f(eq['P'])} C*={_f(eq['C'])} | itératif S={_f(it['S'],1)}") | |
| 474 | + el = elasticites_4q(p) | |
| 475 | + print(f"Élasticités : construction/prix = {el['eps_C_P']:.2f} ; kappa = {el['kappa']:.4f} ; stock stationnaire/demande = {el['eps_S_a']:.3f}") | |
| 476 | + sc = statique_comparative(p) | |
| 477 | + pd.set_option("display.width", 200); pd.set_option("display.float_format", lambda x: f"{x:,.2f}") | |
| 478 | + print(sc[["R_ct", "P_ct", "S_lt", "R_lt", "P_lt", "C_lt", "dS_pct", "dR_ct_pct", "dR_lt_pct", "dP_ct_pct", "dP_lt_pct"]]) | |
| 479 | + | |
| 480 | + print("\n=== 2. Dynamique stock-flux (SYNTHÉTIQUE) ===") | |
| 481 | + pc = ParametresCycle() | |
| 482 | + df = simuler_cycle(pc); ss = etat_stationnaire_cycle(pc); r = resume_cycle(df, pc) | |
| 483 | + print(f"Stationnaire après choc : S={_f(ss['S'])} R={_f(ss['R'])} ({ss['R']/12:.0f} $/mois) P={_f(ss['P'])} C={_f(ss['C'])}/an") | |
| 484 | + print({k: (round(v, 2) if isinstance(v, float) else v) for k, v in r.items()}) | |
| 485 | + for L in (1, 2, 4): | |
| 486 | + rc = racines_caracteristiques(el["kappa"], p.delta, L) | |
| 487 | + print(f"Racines (kappa={el['kappa']:.3f}, L={L}) : module={rc['module']:.3f} oscille={rc['oscille']} période={rc['periode']:.1f} ans stable={rc['stable']}") | |
| 488 | + for k in (0.12, 0.30, 0.60, 1.20): | |
| 489 | + c = condition_oscillation_L1(k, p.delta) | |
| 490 | + print(f"L=1, kappa={k}: seuil={c['seuil']:.4f} oscille={c['oscille']} module={c['module']:.3f} période={c['periode']:.2f} stable={c['stable']}") | |
| 491 | + sens = sensibilite_cycle(pc) | |
| 492 | + print(sens[(sens["lam"] == 0.5)][["L", "mult_c1", "R_pointe_pct", "R_creux_pct", "v_max_pct", "C_max_annuel", "periode_trim", "amortissement"]].to_string(index=False)) | |
| 493 | + | |
| 494 | + print("\n=== 3. Données réelles ===") | |
| 495 | + ann = mises_en_chantier_annuelles() | |
| 496 | + cols = ["total", "individuelles", "collectifs", "part_individuelles", "part_collectifs", "trimestres"] | |
| 497 | + print(ann.loc[[1990, 2000, 2005, 2010, 2015, 2020, 2021, 2022, 2023, 2024, 2025, 2026], cols]) | |
| 498 | + mtl = mises_en_chantier_montreal_annuelles(); print("Montréal RMR :", mtl.loc[[2024, 2025, 2026]].to_dict()) | |
| 499 | + ch = changements_taux_directeur(); print(ch) | |
| 500 | + print(taux_effectifs_a4()) | |
| 501 | + cu = serie_cout_usage() | |
| 502 | + print(cu.loc[DATES_TABLEAU_COUT_USAGE, ["hyp5", "gdc5", "pi", "g_ipln", "reel", "u_ipc", "pr_ipc", "u_ipln", "pr_ipln"]]) | |
| 503 | + print("Dernier mois :", cu.index[-1].date(), cu.iloc[-1][["hyp5", "pi", "u_ipc", "pr_ipc"]].to_dict()) | |
| 504 | + print("IPC Québec sept. 2026 :", ipc_quebec().iloc[-1], "; a/a =", round((ipc_quebec().iloc[-1] / ipc_quebec().iloc[-13] - 1) * 100, 2)) | |
| 505 | + print("IPLN Québec sept. 2026 :", ipln_quebec().iloc[-1], "; a/a =", round((ipln_quebec().iloc[-1] / ipln_quebec().iloc[-13] - 1) * 100, 2)) | |
| 506 | + ex = exemple_cout_usage() | |
| 507 | + for k, v in ex.items(): | |
| 508 | + print(f" {k}: {v:,.4f}" if isinstance(v, float) else f" {k}: {v}") | |
| 509 | + print(indicateurs_apciq()) | |
| 510 | + | |
| 511 | + | |
| 512 | +if __name__ == "__main__": | |
| 513 | + main() | |
added
analysis/python/cases/ch04_marches.py
+333 −0
@@ -0,0 +1,333 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Chapitre 4 — Marchés résidentiels québécois : lecture des DONNÉES RÉELLES de data/raw et calculs cités dans le texte. | |
| 3 | + | |
| 4 | +Imprime tous les chiffres du chapitre 4 : | |
| 5 | + * structure du parc (Recensement 2021, tableau 98-10-0240-01, WDS) : ménages par type de construction et mode | |
| 6 | + d'occupation, Québec et Canada ; parts, taux de propriété par type, indice de dissimilarité Québec/Canada ; | |
| 7 | + * ménages et locataires par RMR (98-10-0231-01) ; population des RMR 2020-2025 (17-10-0148-01) et TCAC ; | |
| 8 | + * Indice des prix des logements neufs (18-10-0205-01, déc. 2016 = 100) : niveaux de septembre 2026, variations, | |
| 9 | + composantes maison/terrain, épisode 2020-2022 ; | |
| 10 | + * marché locatif SCHL (Enquête sur les logements locatifs, octobre 2025, tableaux 1.1.1, 1.1.2, 1.1.3, 1.1.5 du | |
| 11 | + fichier rmr-quebec-2025-fr.xlsx) : inoccupation, loyers, univers, variation estimative vs variation brute ; | |
| 12 | + * indicateurs APCIQ T3 2026 (chiffres cités, calibration.json) : mois d'inventaire, rapports de prix, MRB implicite ; | |
| 13 | + * Banque du Canada (API Valet) : taux directeur, taux hypothécaire affiché 5 ans, obligation 5 ans, 2000-2026 ; | |
| 14 | + * droits sur les mutations immobilières 2026 (province, Montréal, Gatineau, Québec) ; sensibilité du prix soutenable | |
| 15 | + au taux hypothécaire (traite.finance.Hypotheque, composition semestrielle). | |
| 16 | + | |
| 17 | +Usage (racine du dépôt) : PYTHONPATH=analysis/python python3 analysis/python/cases/ch04_marches.py | |
| 18 | +Aucune donnée synthétique n'est utilisée dans ce chapitre. Licences : voir data/raw/SOURCES.md.""" | |
| 19 | +from __future__ import annotations | |
| 20 | +import sys, json, csv, pathlib, math | |
| 21 | +import numpy as np, pandas as pd | |
| 22 | +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1])) | |
| 23 | +from traite import finance as fin | |
| 24 | + | |
| 25 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 26 | +RAW = ROOT / "data/raw" | |
| 27 | +CAL = json.loads((ROOT / "data/synthetic/calibration.json").read_text(encoding="utf-8")) | |
| 28 | + | |
| 29 | +# ----------------------------------------------------------------------------- Recensement 2021 : types et mode d'occupation | |
| 30 | +# Membres de la dimension « Type de construction résidentielle » du tableau 98-10-0240-01 (métadonnées WDS, getCubeMetadata, 2026-10-31) | |
| 31 | +TYPES_240 = {2: "Maison individuelle non attenante", 9: "Maison jumelée", 8: "Maison en rangée", 5: "Appartement ou plain-pied dans un duplex", | |
| 32 | + 6: "Appartement, immeuble de moins de cinq étages", 3: "Appartement, immeuble de cinq étages ou plus", | |
| 33 | + 7: "Autre maison individuelle attenante", 10: "Logement mobile"} | |
| 34 | +TYPES_COURTS = {2: "Maison individuelle", 9: "Jumelée", 8: "En rangée", 5: "Duplex", 6: "Appartement < 5 étages", | |
| 35 | + 3: "Appartement ≥ 5 étages", 7: "Autre attenante", 10: "Mobile"} | |
| 36 | +MODES_240 = {1: "total", 2: "proprietaire", 3: "locataire"} | |
| 37 | +GEO_240 = {1: "Canada", 884: "Québec"} | |
| 38 | + | |
| 39 | +def recensement_types() -> pd.DataFrame: | |
| 40 | + """Ménages privés selon le type de construction et le mode d'occupation (Canada, Québec), Recensement 2021.""" | |
| 41 | + d = json.loads((RAW / "statcan_recensement2021/98100240_canada_quebec_type_construction_mode_occupation.json").read_text(encoding="utf-8")) | |
| 42 | + rows = [] | |
| 43 | + for e in d: | |
| 44 | + o = e["object"]; c = [int(x) for x in o["coordinate"].split(".")] | |
| 45 | + geo, typ, mode = c[0], c[4], c[5] | |
| 46 | + rows.append({"geo": GEO_240[geo], "type_id": typ, "mode": MODES_240[mode], "menages": o["vectorDataPoint"][0]["value"]}) | |
| 47 | + df = pd.DataFrame(rows).pivot_table(index=["geo", "type_id"], columns="mode", values="menages").reset_index() | |
| 48 | + df["type"] = df["type_id"].map(lambda k: TYPES_240.get(k, "Total" if k == 1 else "Autre logement attenant (agrégat)")) | |
| 49 | + df["court"] = df["type_id"].map(lambda k: TYPES_COURTS.get(k, "Total" if k == 1 else "Attenant (agrégat)")) | |
| 50 | + tot = df[df["type_id"] == 1].set_index("geo")["total"] | |
| 51 | + df["part"] = df.apply(lambda r: r["total"] / tot[r["geo"]], axis=1) | |
| 52 | + df["taux_propriete"] = df["proprietaire"] / df["total"] | |
| 53 | + return df | |
| 54 | + | |
| 55 | +def structure_parc() -> pd.DataFrame: | |
| 56 | + """Huit types détaillés (sans l'agrégat 4 ni le total), Québec et Canada côte à côte.""" | |
| 57 | + df = recensement_types() | |
| 58 | + d = df[df["type_id"].isin(TYPES_240)].copy() | |
| 59 | + q = d[d["geo"] == "Québec"].set_index("type_id"); c = d[d["geo"] == "Canada"].set_index("type_id") | |
| 60 | + out = pd.DataFrame({"type": q["type"], "court": q["court"], "qc_menages": q["total"], "qc_part": q["part"], "qc_proprietaires": q["proprietaire"], | |
| 61 | + "qc_locataires": q["locataire"], "qc_taux_propriete": q["taux_propriete"], "ca_menages": c["total"], "ca_part": c["part"], | |
| 62 | + "ca_taux_propriete": c["taux_propriete"]}) | |
| 63 | + return out.sort_values("qc_menages", ascending=False) | |
| 64 | + | |
| 65 | +def indice_dissimilarite(parts_a: np.ndarray, parts_b: np.ndarray) -> float: | |
| 66 | + """D = ½ Σ |s_i^A − s_i^B| : part des ménages à « reclasser » pour que les deux structures coïncident.""" | |
| 67 | + return 0.5 * float(np.abs(np.asarray(parts_a) - np.asarray(parts_b)).sum()) | |
| 68 | + | |
| 69 | +def menages_rmr() -> pd.DataFrame: | |
| 70 | + """98-10-0231-01 : ménages propriétaires / locataires, Canada, Québec, RMR Montréal, Québec, Ottawa-Gatineau (ensemble).""" | |
| 71 | + geo = {1: "Canada", 24: "Québec (province)", 35: "RMR Montréal", 36: "RMR Québec", 81: "RMR Ottawa-Gatineau (ensemble)"} | |
| 72 | + d = json.loads((RAW / "statcan_recensement2021/98100231_mode_occupation_canada_quebec_rmr.json").read_text(encoding="utf-8")) | |
| 73 | + rows = {} | |
| 74 | + for e in d: | |
| 75 | + o = e["object"]; c = [int(x) for x in o["coordinate"].split(".")] | |
| 76 | + rows.setdefault(geo[c[0]], {})[MODES_240[c[6]]] = o["vectorDataPoint"][0]["value"] | |
| 77 | + df = pd.DataFrame(rows).T | |
| 78 | + df["part_locataires"] = df["locataire"] / df["total"] | |
| 79 | + return df | |
| 80 | + | |
| 81 | +# ----------------------------------------------------------------------------- Population des RMR (17-10-0148-01) | |
| 82 | +RMR_148 = {14: "Montréal", 10: "Québec", 17: "Ottawa-Gatineau (partie du Québec)", 11: "Sherbrooke", 12: "Trois-Rivières", 9: "Saguenay", | |
| 83 | + 13: "Drummondville", 191: "Ensemble des RMR du Québec"} | |
| 84 | + | |
| 85 | +def population_rmr() -> pd.DataFrame: | |
| 86 | + d = json.loads((RAW / "statcan_17100148/17100148_rmr_quebec_total_latest6.json").read_text(encoding="utf-8")) | |
| 87 | + rows = {} | |
| 88 | + for e in d: | |
| 89 | + o = e["object"]; k = int(o["coordinate"].split(".")[0]) | |
| 90 | + rows[RMR_148[k]] = {int(p["refPerRaw"][:4]): p["value"] for p in o["vectorDataPoint"]} | |
| 91 | + df = pd.DataFrame(rows).T.sort_index(axis=1) | |
| 92 | + df["var_2024_2025"] = df[2025] / df[2024] - 1 | |
| 93 | + df["tcac_2020_2025"] = (df[2025] / df[2020]) ** (1 / 5) - 1 | |
| 94 | + return df | |
| 95 | + | |
| 96 | +# ----------------------------------------------------------------------------- IPLN (18-10-0205-01) | |
| 97 | +IPLN_GEO = {"Canada": "Canada", "Québec": "Québec (province)", "Montréal, Québec": "Montréal", "Québec, Québec": "Québec", | |
| 98 | + "Ottawa-Gatineau, partie Québec, Ontario/Québec": "Gatineau (partie du Québec)", "Sherbrooke, Québec": "Sherbrooke", | |
| 99 | + "Trois-Rivières, Québec": "Trois-Rivières"} | |
| 100 | + | |
| 101 | +def ipln() -> pd.DataFrame: | |
| 102 | + """Séries mensuelles (déc. 2016 = 100) : colonnes date, geo, composante (Total / Maison / Terrain), indice.""" | |
| 103 | + rows = [] | |
| 104 | + with open(RAW / "statcan_18100205/18100205.csv", encoding="utf-8-sig") as f: | |
| 105 | + r = csv.reader(f, delimiter=";"); next(r) | |
| 106 | + for row in r: | |
| 107 | + if row[1] in IPLN_GEO and row[10] not in ("", ".."): | |
| 108 | + comp = {"Total (maison et terrain)": "Total", "Maison seulement": "Maison", "Terrain seulement": "Terrain"}[row[3]] | |
| 109 | + rows.append({"date": pd.Timestamp(row[0] + "-01"), "geo": IPLN_GEO[row[1]], "composante": comp, "indice": float(row[10]), "vecteur": row[8]}) | |
| 110 | + return pd.DataFrame(rows).sort_values(["geo", "composante", "date"]).reset_index(drop=True) | |
| 111 | + | |
| 112 | +def ipln_stats(df: pd.DataFrame) -> pd.DataFrame: | |
| 113 | + """Par géographie : dernier mois, niveau total/maison/terrain, variation 12 mois, variation depuis déc. 2016, épisode janv. 2020 → déc. 2022, sommet.""" | |
| 114 | + out = [] | |
| 115 | + for geo, g in df.groupby("geo"): | |
| 116 | + t = g[g["composante"] == "Total"].set_index("date")["indice"] | |
| 117 | + last = t.index.max() | |
| 118 | + rec = {"geo": geo, "dernier_mois": last.strftime("%Y-%m"), "total": t[last], "var_12_mois": t[last] / t[last - pd.DateOffset(years=1)] - 1, | |
| 119 | + "var_depuis_2016_12": t[last] / t[pd.Timestamp("2016-12-01")] - 1, | |
| 120 | + "var_2020_01_2022_12": t[pd.Timestamp("2022-12-01")] / t[pd.Timestamp("2020-01-01")] - 1, | |
| 121 | + "sommet": t.idxmax().strftime("%Y-%m"), "sommet_val": t.max(), "ecart_sommet": t[last] / t.max() - 1, | |
| 122 | + "var_2000_2016": t[pd.Timestamp("2016-12-01")] / t[pd.Timestamp("2000-01-01")] - 1 if pd.Timestamp("2000-01-01") in t.index else np.nan} | |
| 123 | + for comp in ("Maison", "Terrain"): | |
| 124 | + s = g[g["composante"] == comp].set_index("date")["indice"] | |
| 125 | + rec[comp.lower()] = s.get(last, np.nan) | |
| 126 | + rec[f"{comp.lower()}_var_depuis_2016_12"] = s.get(last, np.nan) / s.get(pd.Timestamp("2016-12-01"), np.nan) - 1 | |
| 127 | + out.append(rec) | |
| 128 | + return pd.DataFrame(out).set_index("geo") | |
| 129 | + | |
| 130 | +# ----------------------------------------------------------------------------- SCHL, Enquête sur les logements locatifs (octobre 2025) | |
| 131 | +RMR_SCHL = {"Montréal (RMR)": "Montréal", "Québec (RMR)": "Québec", "Ottawa-Gatineau (RMR) (partie Québec)": "Gatineau (partie du Québec)", | |
| 132 | + "Sherbrooke (RMR)": "Sherbrooke", "Trois-Rivières (RMR)": "Trois-Rivières", "Saguenay (RMR)": "Saguenay"} | |
| 133 | +_SKIP = {"a", "b", "c", "d", "↑", "↓", "-", "n.d."} | |
| 134 | +TYPES_SCHL = ["studio", "1_chambre", "2_chambres", "3_chambres_plus", "tous"] | |
| 135 | + | |
| 136 | +def _val(tok): | |
| 137 | + if tok is None: return None | |
| 138 | + s = str(tok).strip().replace(" ", " ") | |
| 139 | + if s in ("**", "++", ""): return np.nan # ** : donnée supprimée ; ++ : variation non calculable | |
| 140 | + try: return float(s.replace(" ", "").replace(",", ".")) | |
| 141 | + except ValueError: return None | |
| 142 | + | |
| 143 | +def _lire_tableau(ws) -> dict: | |
| 144 | + """Pour chaque centre : 10 valeurs (5 types × [oct. 2024, oct. 2025]) nettoyées des lettres de qualité et des flèches.""" | |
| 145 | + out = {} | |
| 146 | + for row in ws.iter_rows(values_only=True): | |
| 147 | + cells = [c for c in row if c is not None] | |
| 148 | + if not cells: continue | |
| 149 | + lab = str(cells[0]).strip() | |
| 150 | + for k, name in RMR_SCHL.items(): | |
| 151 | + if lab.startswith(k.split(" (RMR)")[0]) and "(RMR)" in lab: | |
| 152 | + vals = [] | |
| 153 | + for tok in cells[1:]: | |
| 154 | + s = str(tok).strip() | |
| 155 | + if s in _SKIP: continue | |
| 156 | + v = _val(tok) | |
| 157 | + if v is None: continue | |
| 158 | + vals.append(v) | |
| 159 | + if len(vals) == 10: | |
| 160 | + out[name] = vals | |
| 161 | + return out | |
| 162 | + | |
| 163 | +def schl_rmr() -> pd.DataFrame: | |
| 164 | + """Inoccupation (%), loyer moyen ($), univers (logements) et variation estimative (%) par RMR, octobre 2024 et 2025 (fichier Québec, tous les centres).""" | |
| 165 | + import openpyxl | |
| 166 | + wb = openpyxl.load_workbook(RAW / "schl_rml_2025/rmr-quebec-2025-fr.xlsx", read_only=True, data_only=True) | |
| 167 | + t111, t112, t113, t115 = (_lire_tableau(wb[f"Tableau 1.1.{i}"]) for i in (1, 2, 3, 5)) | |
| 168 | + rows = [] | |
| 169 | + for name in RMR_SCHL.values(): | |
| 170 | + rec = {"rmr": name} | |
| 171 | + for j, typ in enumerate(TYPES_SCHL): | |
| 172 | + rec[f"inocc_{typ}_2024"], rec[f"inocc_{typ}_2025"] = t111[name][2 * j], t111[name][2 * j + 1] | |
| 173 | + rec[f"loyer_{typ}_2024"], rec[f"loyer_{typ}_2025"] = t112[name][2 * j], t112[name][2 * j + 1] | |
| 174 | + rec[f"univers_{typ}_2024"], rec[f"univers_{typ}_2025"] = t113[name][2 * j], t113[name][2 * j + 1] | |
| 175 | + rec[f"varest_{typ}_2024"], rec[f"varest_{typ}_2025"] = t115[name][2 * j], t115[name][2 * j + 1] | |
| 176 | + rec["var_brute_2_chambres"] = rec["loyer_2_chambres_2025"] / rec["loyer_2_chambres_2024"] - 1 | |
| 177 | + rec["var_brute_tous"] = rec["loyer_tous_2025"] / rec["loyer_tous_2024"] - 1 | |
| 178 | + rows.append(rec) | |
| 179 | + return pd.DataFrame(rows).set_index("rmr") | |
| 180 | + | |
| 181 | +# ----------------------------------------------------------------------------- APCIQ T3 2026 (chiffres cités avec attribution, calibration.json) | |
| 182 | +def apciq_t3_2026() -> pd.DataFrame: | |
| 183 | + """Médianes et volumes T3 2026 (APCIQ par le système Centris) tels que notés dans calibration.json ; mois d'inventaire = inscriptions / (ventes trimestrielles / 3).""" | |
| 184 | + p = CAL["prix_apciq"] | |
| 185 | + rows = { | |
| 186 | + "Province": dict(unifamiliale=p["province"]["unifamiliale_prix_median"]["valeur"], copropriete=p["province"]["copropriete_prix_median"]["valeur"], | |
| 187 | + plex=p["province"]["plex_prix_median"]["valeur"], ventes=p["province"]["ventes_residentielles"]["valeur"], inscriptions=p["province"]["inscriptions_en_vigueur"]["valeur"]), | |
| 188 | + "Montréal": dict(unifamiliale=645500, copropriete=435000, plex=860500, ventes=9265, inscriptions=20350), | |
| 189 | + "Québec": dict(unifamiliale=463500, copropriete=334900, plex=600000, ventes=2161, inscriptions=2204), | |
| 190 | + "Gatineau": dict(unifamiliale=480000, copropriete=297450, plex=617500, ventes=1044, inscriptions=2256), | |
| 191 | + "Sherbrooke": dict(unifamiliale=490750, copropriete=327000, plex=544500, ventes=569, inscriptions=825), | |
| 192 | + "Trois-Rivières": dict(unifamiliale=395000, copropriete=287000, plex=420000, ventes=372, inscriptions=447), | |
| 193 | + "Saguenay": dict(unifamiliale=365500, copropriete=np.nan, plex=373500, ventes=371, inscriptions=498), | |
| 194 | + } | |
| 195 | + for k in ("Montréal", "Québec", "Gatineau", "Sherbrooke"): # cohérence avec calibration.json quand la RMR y figure | |
| 196 | + key = {"Montréal": "rmr_montreal", "Québec": "rmr_quebec", "Gatineau": "rmr_gatineau", "Sherbrooke": "rmr_sherbrooke"}[k] | |
| 197 | + assert p[key]["unifamiliale_prix_median"]["valeur"] == rows[k]["unifamiliale"], k | |
| 198 | + df = pd.DataFrame(rows).T | |
| 199 | + df["mois_inventaire"] = df["inscriptions"] / (df["ventes"] / 3) | |
| 200 | + df["plex_sur_unifamiliale"] = df["plex"] / df["unifamiliale"] | |
| 201 | + df["copro_sur_unifamiliale"] = df["copropriete"] / df["unifamiliale"] | |
| 202 | + return df | |
| 203 | + | |
| 204 | +def mrb_implicite(prix_plex: float, loyer_2ch: float, logements: int = 3) -> float: | |
| 205 | + """Multiplicateur du revenu brut implicite d'un plex de `logements` logements loués au loyer moyen 2 chambres de la RMR (ordre de grandeur).""" | |
| 206 | + return fin.multiplicateur_revenu_brut(prix_plex, 12 * logements * loyer_2ch) | |
| 207 | + | |
| 208 | +# ----------------------------------------------------------------------------- Banque du Canada (Valet) | |
| 209 | +def _valet(fichier: str) -> pd.Series: | |
| 210 | + rows = [] | |
| 211 | + with open(RAW / "bdc_valet" / fichier, encoding="utf-8-sig") as f: | |
| 212 | + started = False | |
| 213 | + for line in f: | |
| 214 | + line = line.strip() | |
| 215 | + if line.startswith('"OBSERVATIONS"'): started = True; continue | |
| 216 | + if not started or not line or line.startswith('"date"'): continue | |
| 217 | + parts = [x.strip('"') for x in line.split(",")] | |
| 218 | + if len(parts) >= 2 and parts[1] not in ("", "na"): | |
| 219 | + rows.append((pd.Timestamp(parts[0]), float(parts[1]))) | |
| 220 | + return pd.Series(dict(rows)).sort_index() | |
| 221 | + | |
| 222 | +def bdc() -> dict: | |
| 223 | + s = {"directeur": _valet("V39079.csv"), "hyp5": _valet("V80691335.csv"), "obl5": _valet("BD.CDN.5YR.DQ.YLD.csv"), "obl10": _valet("BD.CDN.10YR.DQ.YLD.csv")} | |
| 224 | + d = s["directeur"] | |
| 225 | + chg = d[d.diff() != 0].dropna() | |
| 226 | + stats = {"directeur_dernier": (d.index[-1].date().isoformat(), d.iloc[-1]), "directeur_min": (d.idxmin().date().isoformat(), d.min()), | |
| 227 | + "directeur_max_2000": (d.idxmax().date().isoformat(), d.max()), | |
| 228 | + "directeur_2022_03": d[d.index >= "2022-03-01"].iloc[0], "directeur_2023_07_12": d[d.index >= "2023-07-13"].iloc[0], | |
| 229 | + "hyp5_dernier": (s["hyp5"].index[-1].date().isoformat(), s["hyp5"].iloc[-1]), "hyp5_min": (s["hyp5"].idxmin().date().isoformat(), s["hyp5"].min()), | |
| 230 | + "hyp5_max_2000": (s["hyp5"].idxmax().date().isoformat(), s["hyp5"].max()), | |
| 231 | + "obl5_dernier": (s["obl5"].index[-1].date().isoformat(), s["obl5"].iloc[-1]), "obl10_dernier": (s["obl10"].index[-1].date().isoformat(), s["obl10"].iloc[-1]), | |
| 232 | + "changements_2022_2026": [(t.date().isoformat(), v) for t, v in chg[chg.index >= "2022-01-01"].items()]} | |
| 233 | + return {"series": s, "stats": stats} | |
| 234 | + | |
| 235 | +# ----------------------------------------------------------------------------- Droits sur les mutations immobilières 2026 | |
| 236 | +BAREMES_2026 = { | |
| 237 | + # (borne supérieure de la tranche, taux) ; None = au-delà. Sources : LDMI art. 2 (province) ; montreal.ca ; gatineau.ca (dès le 25 février 2026) ; ville.quebec.qc.ca | |
| 238 | + "Province (barème légal)": [(62900, 0.005), (315000, 0.01), (None, 0.015)], | |
| 239 | + "Montréal": [(62900, 0.005), (315000, 0.01), (552300, 0.015), (1104700, 0.02), (2136500, 0.025), (3113000, 0.035), (None, 0.04)], | |
| 240 | + "Gatineau (dès le 25 février 2026)": [(62900, 0.005), (315000, 0.01), (500000, 0.015), (None, 0.03)], | |
| 241 | + "Québec (ville)": [(62900, 0.005), (315000, 0.01), (500000, 0.015), (750000, 0.025), (None, 0.03)], | |
| 242 | +} | |
| 243 | + | |
| 244 | +def droits_mutation(base: float, bareme: list[tuple]) -> float: | |
| 245 | + """Droit progressif par tranches : Σ_k τ_k × (min(B, b_k) − b_{k−1})⁺.""" | |
| 246 | + total, prev = 0.0, 0.0 | |
| 247 | + for borne, taux in bareme: | |
| 248 | + haut = base if borne is None else min(base, borne) | |
| 249 | + if haut > prev: total += taux * (haut - prev) | |
| 250 | + prev = borne if borne is not None else base | |
| 251 | + if borne is not None and base <= borne: break | |
| 252 | + return total | |
| 253 | + | |
| 254 | +def tableau_mutations(prix: list[float]) -> pd.DataFrame: | |
| 255 | + rows = [] | |
| 256 | + for nom, bar in BAREMES_2026.items(): | |
| 257 | + for p in prix: | |
| 258 | + d = droits_mutation(p, bar) | |
| 259 | + rows.append({"bareme": nom, "prix": p, "droit": d, "taux_effectif": d / p}) | |
| 260 | + return pd.DataFrame(rows) | |
| 261 | + | |
| 262 | +# ----------------------------------------------------------------------------- Taux et prix soutenable | |
| 263 | +def paiement_mensuel(capital: float, taux: float, amort_ans: int = 25) -> float: | |
| 264 | + return fin.Hypotheque(capital, taux, amort_ans * 12).paiement | |
| 265 | + | |
| 266 | +def prix_soutenable(paiement: float, taux: float, mise_de_fonds: float = 0.20, amort_ans: int = 25) -> float: | |
| 267 | + """Prix tel que le paiement mensuel d'un prêt de (1 − m) × prix, au taux donné (composition semestrielle), égale `paiement`.""" | |
| 268 | + i = fin.taux_mensuel_canadien(taux) | |
| 269 | + capital = paiement * fin.facteur_va_annuite(i, amort_ans * 12) | |
| 270 | + return capital / (1 - mise_de_fonds) | |
| 271 | + | |
| 272 | +def sensibilite_taux(prix_ref: float = 500000.0, taux_ref: float = 0.0412, taux: tuple = (0.03, 0.035, 0.0412, 0.045, 0.05, 0.055, 0.0619, 0.07)) -> pd.DataFrame: | |
| 273 | + """Prix médian provincial T3 2026 (500 000 $), mise de fonds 20 %, 25 ans : paiement au taux de référence (assuré fixe ≥ 5 ans, août 2026 : 4,12 %), | |
| 274 | + puis prix soutenable à paiement constant pour chaque taux.""" | |
| 275 | + cap = prix_ref * 0.8 | |
| 276 | + pmt_ref = paiement_mensuel(cap, taux_ref) | |
| 277 | + rows = [] | |
| 278 | + for t in taux: | |
| 279 | + rows.append({"taux": t, "paiement_pour_prix_ref": paiement_mensuel(cap, t), "prix_soutenable": prix_soutenable(pmt_ref, t), | |
| 280 | + "variation_prix": prix_soutenable(pmt_ref, t) / prix_ref - 1}) | |
| 281 | + return pd.DataFrame(rows), pmt_ref | |
| 282 | + | |
| 283 | +# ----------------------------------------------------------------------------- Ratio prix / loyer annuel | |
| 284 | +def ratio_prix_loyer(prix: float, loyer_mensuel: float) -> float: | |
| 285 | + return prix / (12 * loyer_mensuel) | |
| 286 | + | |
| 287 | +# ----------------------------------------------------------------------------- Impression | |
| 288 | +def fmt(x, d=0): return f"{x:,.{d}f}".replace(",", " ") | |
| 289 | + | |
| 290 | +def main(verbose: bool = True) -> dict: | |
| 291 | + res = {} | |
| 292 | + sp = structure_parc(); res["parc"] = sp | |
| 293 | + rt = recensement_types(); res["types"] = rt | |
| 294 | + tot = rt[rt["type_id"] == 1].set_index("geo") | |
| 295 | + D = indice_dissimilarite(sp["qc_part"].values, sp["ca_part"].values); res["dissimilarite"] = D | |
| 296 | + mr = menages_rmr(); res["menages_rmr"] = mr | |
| 297 | + pop = population_rmr(); res["population"] = pop | |
| 298 | + ip = ipln(); st = ipln_stats(ip); res["ipln"] = ip; res["ipln_stats"] = st | |
| 299 | + sc = schl_rmr(); res["schl"] = sc | |
| 300 | + ap = apciq_t3_2026(); res["apciq"] = ap | |
| 301 | + b = bdc(); res["bdc"] = b | |
| 302 | + mut = tableau_mutations([500000, 645500, 860500, 1500000]); res["mutations"] = mut | |
| 303 | + sens, pmt_ref = sensibilite_taux(); res["sensibilite"] = sens; res["paiement_ref"] = pmt_ref | |
| 304 | + # MRB et prix/loyer implicites | |
| 305 | + mrb = {r: mrb_implicite(ap.loc[r, "plex"], sc.loc[sc.index[sc.index.str.startswith(r.split(" ")[0])][0], "loyer_2_chambres_2025"]) | |
| 306 | + for r in ["Montréal", "Québec", "Gatineau", "Sherbrooke", "Trois-Rivières", "Saguenay"]} | |
| 307 | + res["mrb"] = mrb | |
| 308 | + if not verbose: return res | |
| 309 | + print("=== Recensement 2021 (98-10-0240-01) — ménages privés") | |
| 310 | + for g in ("Québec", "Canada"): | |
| 311 | + print(f" {g} : total {fmt(tot.loc[g,'total'])} ; propriétaires {fmt(tot.loc[g,'proprietaire'])} ({tot.loc[g,'taux_propriete']*100:.1f} %) ; locataires {fmt(tot.loc[g,'locataire'])}") | |
| 312 | + print(sp[["court", "qc_menages", "qc_part", "qc_taux_propriete", "ca_part", "ca_taux_propriete"]].to_string(float_format=lambda v: f"{v:.4f}")) | |
| 313 | + print(f" Indice de dissimilarité Québec/Canada (8 types) : {D:.4f}") | |
| 314 | + agg = rt[(rt["type_id"] == 4)].set_index("geo"); print(f" Agrégat « autre logement attenant » : Québec {fmt(agg.loc['Québec','total'])} ({agg.loc['Québec','part']*100:.1f} %), Canada {agg.loc['Canada','part']*100:.1f} %") | |
| 315 | + plex_like = sp.loc[[5, 6], "qc_menages"].sum(); print(f" Duplex + appartements < 5 étages, Québec : {fmt(plex_like)} ménages = {plex_like/tot.loc['Québec','total']*100:.1f} %") | |
| 316 | + print("\n=== Ménages par RMR (98-10-0231-01)"); print(mr.to_string(float_format=lambda v: f"{v:.4f}")) | |
| 317 | + print("\n=== Population des RMR au 1er juillet (17-10-0148-01)"); print(pop.to_string(float_format=lambda v: f"{v:.4f}")) | |
| 318 | + print("\n=== IPLN (18-10-0205-01, déc. 2016 = 100)"); print(st.to_string(float_format=lambda v: f"{v:.4f}")) | |
| 319 | + print("\n=== SCHL — ELL octobre 2025 (fichier rmr-quebec-2025-fr.xlsx, tableaux 1.1.1/1.1.2/1.1.3/1.1.5)") | |
| 320 | + cols = ["inocc_tous_2024", "inocc_tous_2025", "inocc_2_chambres_2025", "loyer_2_chambres_2024", "loyer_2_chambres_2025", "loyer_tous_2025", "univers_tous_2025", | |
| 321 | + "varest_2_chambres_2025", "varest_tous_2025", "var_brute_2_chambres", "var_brute_tous"] | |
| 322 | + print(sc[cols].to_string(float_format=lambda v: f"{v:.4f}")) | |
| 323 | + print("\n=== APCIQ T3 2026 (chiffres cités) et indicateurs dérivés"); print(ap.to_string(float_format=lambda v: f"{v:.3f}")) | |
| 324 | + print(" MRB implicite (plex 2-5 log. ↔ 3 logements au loyer moyen 2 ch. SCHL oct. 2025) :", {k: round(v, 2) for k, v in mrb.items()}) | |
| 325 | + print(f" Ratio prix/loyer annuel, Montréal (copropriété 435 000 $ / loyer copropriété locative 2 ch. 1 826 $) : {ratio_prix_loyer(435000, 1826):.1f}") | |
| 326 | + print("\n=== Banque du Canada (Valet)") | |
| 327 | + for k, v in b["stats"].items(): print(f" {k} : {v}") | |
| 328 | + print("\n=== Droits sur les mutations immobilières 2026"); print(mut.to_string(float_format=lambda v: f"{v:.4f}")) | |
| 329 | + print(f"\n=== Sensibilité au taux : paiement de référence {pmt_ref:.2f} $/mois (prêt 400 000 $, 4,12 %, 25 ans)"); print(sens.to_string(float_format=lambda v: f"{v:.4f}")) | |
| 330 | + return res | |
| 331 | + | |
| 332 | +if __name__ == "__main__": | |
| 333 | + main() | |
added
analysis/python/cases/ch06_demembrements.py
+280 −0
@@ -0,0 +1,280 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Chapitre 6 — Code civil et droit immobilier : effets des modalités et démembrements du droit de propriété sur la valeur. | |
| 3 | + | |
| 4 | +Imprime tous les chiffres cités dans le chapitre 6 (DONNÉES SYNTHÉTIQUES et hypothèses pédagogiques déclarées) : | |
| 5 | + * table de mortalité SYNTHÉTIQUE (loi de Gompertz–Makeham à paramètres déclarés ; ce n'est PAS une table de | |
| 6 | + Statistique Canada) : probabilités de survie et espérances de vie ; | |
| 7 | + * usufruit à terme fixe et usufruit viager (art. 1120, 1123, 1162 C.c.Q.) sur la maison du cas 1 (Gatineau) : | |
| 8 | + valeur de l'usufruit U, de la nue-propriété NP, identité U + NP = V, rente viagère équivalente (art. 1171) ; | |
| 9 | + * emphytéose (art. 1195, 1197, 1208-1210) sur un immeuble locatif dont le RNE est celui du cas 3 (Montréal) : | |
| 10 | + valeur du droit de l'emphytéote et du droit du propriétaire selon la durée restante ; seuils de 40 ans (D-15.1, art. 1) | |
| 11 | + et de 50 ans (art. 1040 C.c.Q.) ; | |
| 12 | + * part indivise (art. 1010, 1015) avec décote déclarée ; servitude de non-construction (art. 1177) par l'élasticité | |
| 13 | + prix-terrain du modèle hédonique du cas 1 ; rang hypothécaire (art. 2660, 2945) ; droits sur les mutations 2026 | |
| 14 | + (D-15.1, art. 2) ; quantum d'un vice caché (art. 1726-1728) ; budget de recherche au registre foncier. | |
| 15 | + | |
| 16 | +Usage (racine du dépôt) : PYTHONPATH=analysis/python python3 analysis/python/cases/ch06_demembrements.py | |
| 17 | +Les fonctions d'actualisation proviennent de analysis/python/traite/finance.py (aucune réécriture) ; les valeurs des cas | |
| 18 | +proviennent de traite.cas1 et traite.cas3.""" | |
| 19 | +from __future__ import annotations | |
| 20 | +import sys, math, pathlib | |
| 21 | +import numpy as np, pandas as pd | |
| 22 | +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1])) | |
| 23 | +from traite import finance as fin | |
| 24 | + | |
| 25 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 26 | + | |
| 27 | +# ----------------------------------------------------------------------------- hypothèses déclarées | |
| 28 | +# Table de mortalité SYNTHÉTIQUE : force de mortalité de Gompertz–Makeham mu(x) = A + B c^x. | |
| 29 | +# Paramètres pédagogiques choisis pour une espérance de vie à 65 ans de l'ordre de vingt ans (voir table_mortalite()) ; | |
| 30 | +# ils ne reproduisent aucune table officielle. En pratique, utiliser les tables complètes de mortalité de Statistique | |
| 31 | +# Canada (tableau 13-10-0114-01) ou les tables de l'Institut canadien des actuaires, selon la fin de l'évaluation. | |
| 32 | +GM_A, GM_B, GM_C = 0.0004, 0.000025, 1.102 | |
| 33 | +OMEGA = 111 # âge limite de la table synthétique (survie nulle au-delà) | |
| 34 | + | |
| 35 | +R_ACTU = 0.05 # taux d'actualisation des flux d'une résidence (hypothèse pédagogique) | |
| 36 | +G_LOYER = 0.015 # croissance annuelle de la valeur locative nette (hypothèse pédagogique) | |
| 37 | +V_ARRONDI = 638_000.0 # valeur de la maison du cas 1 (prédiction hédonique 637 953 $ arrondie au millier) | |
| 38 | + | |
| 39 | +# Emphytéose : immeuble dont le RNE est celui du cas 3 ; monde de Gordon r - g = taux global | |
| 40 | +R_EMPH, G_EMPH = 0.07, 0.025 # r = taux d'actualisation du cas 3 (hypothèses DCF) ; g déclaré | |
| 41 | +RENTE_EMPH = 30_000.0 # rente emphytéotique annuelle fixe (hypothèse pédagogique) | |
| 42 | +N_EMPH = 35 # durée restante de référence (années) | |
| 43 | + | |
| 44 | +# Droits sur les mutations immobilières, tranches 2026 (RLRQ, c. D-15.1, art. 2 ; notes-A § 2.3) | |
| 45 | +TRANCHES_2026 = [(62_900.0, 0.005), (315_000.0, 0.010), (float("inf"), 0.015)] | |
| 46 | + | |
| 47 | +# Registre foncier — grille tarifaire en vigueur à compter du 1er avril 2025 (notes-A § 3.1) | |
| 48 | +TARIFS_REGISTRE = {"consultation_document": 1.0, "inscription": 142.0, "radiation": 162.0, | |
| 49 | + "etat_certifie": 16.0, "copie_non_certifiee": 26.0, "copie_certifiee": 42.0} | |
| 50 | + | |
| 51 | + | |
| 52 | +# ----------------------------------------------------------------------------- mortalité synthétique | |
| 53 | + | |
| 54 | +def mu(x: float) -> float: | |
| 55 | + """Force de mortalité synthétique (Gompertz–Makeham).""" | |
| 56 | + return GM_A + GM_B * GM_C ** x | |
| 57 | + | |
| 58 | + | |
| 59 | +def survie(x: float, t: float) -> float: | |
| 60 | + """Probabilité tp_x qu'une personne d'âge x soit vivante à l'âge x + t (intégrale exacte de mu).""" | |
| 61 | + if t <= 0: | |
| 62 | + return 1.0 | |
| 63 | + if x + t >= OMEGA: | |
| 64 | + return 0.0 | |
| 65 | + integ = GM_A * t + GM_B * GM_C ** x * (GM_C ** t - 1.0) / math.log(GM_C) | |
| 66 | + return math.exp(-integ) | |
| 67 | + | |
| 68 | + | |
| 69 | +def esperance_vie(x: float) -> float: | |
| 70 | + """Espérance de vie abrégée (somme des tp_x, correction d'un demi-an).""" | |
| 71 | + return sum(survie(x, t) for t in range(1, OMEGA - int(x) + 1)) + 0.5 | |
| 72 | + | |
| 73 | + | |
| 74 | +def table_mortalite(ages=(55, 60, 65, 70, 75, 80, 85)) -> pd.DataFrame: | |
| 75 | + rows = [{"age": a, "esperance_vie": esperance_vie(a), "p10": survie(a, 10), "p20": survie(a, 20), "p30": survie(a, 30)} for a in ages] | |
| 76 | + return pd.DataFrame(rows).set_index("age") | |
| 77 | + | |
| 78 | + | |
| 79 | +# ----------------------------------------------------------------------------- valeur de la pleine propriété | |
| 80 | + | |
| 81 | +def valeur_cas1() -> dict: | |
| 82 | + """Prédiction hédonique du sujet du cas 1 (traite.cas1) et valeur arrondie retenue dans le chapitre.""" | |
| 83 | + from traite import cas1 | |
| 84 | + t = cas1.tout() | |
| 85 | + return {"prediction_hedonique": float(t["prediction_hedonique"]), "V": V_ARRONDI, | |
| 86 | + "terrain_m2": t["sujet"]["superficie_terrain_m2"], "elasticite_terrain": float(t["modele"]["coef"].loc["ln_T", "coef"])} | |
| 87 | + | |
| 88 | + | |
| 89 | +def loyer_net(V: float, r: float = R_ACTU, g: float = G_LOYER) -> float: | |
| 90 | + """Valeur locative nette annuelle cohérente avec V dans un monde de Gordon : L = (r - g) V.""" | |
| 91 | + return (r - g) * V | |
| 92 | + | |
| 93 | + | |
| 94 | +# ----------------------------------------------------------------------------- usufruit | |
| 95 | + | |
| 96 | +def usufruit_terme(V: float, n: int, r: float = R_ACTU, g: float = G_LOYER) -> dict: | |
| 97 | + """Usufruit à terme fixe de n ans : U = somme actualisée des fruits L(1+g)^(t-1), NP = V - U = V((1+g)/(1+r))^n.""" | |
| 98 | + L = loyer_net(V, r, g) | |
| 99 | + flux = [L * (1 + g) ** (t - 1) for t in range(1, n + 1)] | |
| 100 | + U = fin.van([0.0] + flux, r) | |
| 101 | + NP = V - U | |
| 102 | + return {"n": n, "L": L, "U": U, "NP": NP, "part_NP": NP / V, "NP_formule": V * ((1 + g) / (1 + r)) ** n} | |
| 103 | + | |
| 104 | + | |
| 105 | +def usufruit_viager(V: float, x: int, r: float = R_ACTU, g: float = G_LOYER) -> dict: | |
| 106 | + """Usufruit viager d'un usufruitier d'âge x (convention : le revenu de l'année t revient à l'usufruitier s'il est vivant au | |
| 107 | + début de l'année, probabilité (t-1)p_x ; la nue-propriété reçoit la pleine propriété à la fin de l'année du décès).""" | |
| 108 | + L = loyer_net(V, r, g) | |
| 109 | + U = 0.0; NP_direct = 0.0; esp = 0.0 | |
| 110 | + for t in range(1, OMEGA - x + 1): | |
| 111 | + p_debut, p_fin = survie(x, t - 1), survie(x, t) | |
| 112 | + v = fin.facteur_va(r, t) | |
| 113 | + U += v * p_debut * L * (1 + g) ** (t - 1) | |
| 114 | + NP_direct += v * (p_debut - p_fin) * V * (1 + g) ** t | |
| 115 | + esp += p_fin | |
| 116 | + return {"age": x, "L": L, "U": U, "NP": V - U, "NP_direct": NP_direct, "part_NP": (V - U) / V, | |
| 117 | + "esperance_vie": esp + 0.5, "annuite_viagere": U / L} | |
| 118 | + | |
| 119 | + | |
| 120 | +def rente_viagere_equivalente(V: float, x: int, r: float = R_ACTU, g: float = G_LOYER) -> float: | |
| 121 | + """Rente annuelle constante, versée tant que l'usufruitier vit, de même valeur actuelle que l'usufruit (art. 1171).""" | |
| 122 | + u = usufruit_viager(V, x, r, g) | |
| 123 | + a_x = sum(fin.facteur_va(r, t) * survie(x, t - 1) for t in range(1, OMEGA - x + 1)) | |
| 124 | + return u["U"] / a_x | |
| 125 | + | |
| 126 | + | |
| 127 | +def grille_usufruit(V: float, ages=(55, 60, 65, 70, 75, 80, 85), taux=(0.04, 0.05, 0.06), g: float = G_LOYER) -> pd.DataFrame: | |
| 128 | + """Part de la nue-propriété (NP/V) selon l'âge de l'usufruitier et le taux d'actualisation.""" | |
| 129 | + return pd.DataFrame({f"r={r:.2f}": [usufruit_viager(V, a, r, g)["part_NP"] for a in ages] for r in taux}, index=list(ages)) | |
| 130 | + | |
| 131 | + | |
| 132 | +# ----------------------------------------------------------------------------- emphytéose | |
| 133 | + | |
| 134 | +def emphyteose(noi_1: float, rente: float, n: int, r: float = R_EMPH, g: float = G_EMPH, r_prop: float | None = None) -> dict: | |
| 135 | + """Droits de l'emphytéote et du propriétaire pour une durée restante de n ans. | |
| 136 | + Monde de Gordon : V_pleine = NOI_1/(r-g) ; NOI_t = NOI_1 (1+g)^(t-1) ; reprise à la fin de l'année n à V_n = V_pleine (1+g)^n. | |
| 137 | + Si r_prop est donné, les flux du propriétaire (rente + reprise) sont actualisés à ce taux (sinon à r : la somme vaut V_pleine).""" | |
| 138 | + V = noi_1 / (r - g) | |
| 139 | + rp = r if r_prop is None else r_prop | |
| 140 | + flux_emph = [noi_1 * (1 + g) ** (t - 1) - rente for t in range(1, n + 1)] | |
| 141 | + V_emph = fin.van([0.0] + flux_emph, r) | |
| 142 | + V_rentes = rente * fin.facteur_va_annuite(rp, n) | |
| 143 | + V_reprise = V * (1 + g) ** n * fin.facteur_va(rp, n) | |
| 144 | + V_prop = V_rentes + V_reprise | |
| 145 | + return {"n": n, "V_pleine": V, "V_emph": V_emph, "V_rentes": V_rentes, "V_reprise": V_reprise, "V_prop": V_prop, | |
| 146 | + "somme": V_emph + V_prop, "part_emph": V_emph / V, "rente": rente, "noi_1": noi_1} | |
| 147 | + | |
| 148 | + | |
| 149 | +def grille_emphyteose(noi_1: float, rente: float, durees=(60, 50, 45, 40, 35, 30, 20, 10, 5), **kw) -> pd.DataFrame: | |
| 150 | + return pd.DataFrame([emphyteose(noi_1, rente, n, **kw) for n in durees]).set_index("n") | |
| 151 | + | |
| 152 | + | |
| 153 | +def rne_cas3() -> float: | |
| 154 | + """RNE normalisé de première année du cas 3 (traite.cas3), seul chiffre du cas repris ici.""" | |
| 155 | + from traite import cas3 | |
| 156 | + return float(cas3.tout()["base"]["rne"]) | |
| 157 | + | |
| 158 | + | |
| 159 | +# ----------------------------------------------------------------------------- indivision, servitude, hypothèque, mutations, vice | |
| 160 | + | |
| 161 | +def part_indivise(V: float, quote_part: float, decote: float) -> dict: | |
| 162 | + """Valeur d'une part indivise : quote-part de la valeur du tout, diminuée d'une décote d'illiquidité déclarée.""" | |
| 163 | + brute = quote_part * V | |
| 164 | + return {"quote_part": quote_part, "valeur_proportionnelle": brute, "decote": decote, "valeur_part": brute * (1 - decote)} | |
| 165 | + | |
| 166 | + | |
| 167 | +def servitude_terrain(V: float, terrain_m2: float, grevee_m2: float, elasticite: float) -> dict: | |
| 168 | + """Effet d'une servitude qui soustrait grevee_m2 à l'usage du terrain, par l'élasticité prix-terrain du modèle hédonique : | |
| 169 | + V_avec = V ((T - T_g)/T)^beta ; approximation déclarée (la servitude n'est pas une réduction de lot).""" | |
| 170 | + ratio = (terrain_m2 - grevee_m2) / terrain_m2 | |
| 171 | + V_avec = V * ratio ** elasticite | |
| 172 | + return {"terrain_m2": terrain_m2, "grevee_m2": grevee_m2, "elasticite": elasticite, "V_sans": V, "V_avec": V_avec, | |
| 173 | + "delta": V - V_avec, "delta_pct": 1 - V_avec / V} | |
| 174 | + | |
| 175 | + | |
| 176 | +def rang_hypothecaire(produit_net: float, creances: list[float]) -> list[dict]: | |
| 177 | + """Distribution du produit net d'une vente sous contrôle de justice selon le rang (art. 2660, 2945).""" | |
| 178 | + reste = produit_net; out = [] | |
| 179 | + for i, c in enumerate(creances, 1): | |
| 180 | + paye = min(c, max(reste, 0.0)); reste -= paye | |
| 181 | + out.append({"rang": i, "creance": c, "recouvre": paye, "perte": c - paye, "taux_recouvrement": paye / c if c else 1.0}) | |
| 182 | + out.append({"rang": "débiteur", "creance": 0.0, "recouvre": max(reste, 0.0), "perte": 0.0, "taux_recouvrement": float("nan")}) | |
| 183 | + return out | |
| 184 | + | |
| 185 | + | |
| 186 | +def droit_mutation(base: float, tranches=TRANCHES_2026, taux_municipal: list[tuple[float, float]] | None = None) -> dict: | |
| 187 | + """Droit sur les mutations immobilières (D-15.1, art. 2) : taux par tranche sur la base d'imposition. | |
| 188 | + taux_municipal : liste optionnelle de (seuil_inferieur, taux) remplaçant 1,5 % au-dessus de 500 000 $ (facultatif, max 3 %).""" | |
| 189 | + detail = []; prec = 0.0; total = 0.0 | |
| 190 | + for plafond, taux in tranches: | |
| 191 | + if base <= prec: | |
| 192 | + break | |
| 193 | + assiette = min(base, plafond) - prec | |
| 194 | + montant = assiette * taux | |
| 195 | + detail.append({"de": prec, "a": min(base, plafond), "taux": taux, "montant": montant}); total += montant; prec = plafond | |
| 196 | + supplement = 0.0 | |
| 197 | + if taux_municipal: | |
| 198 | + for seuil, taux in taux_municipal: | |
| 199 | + if base > seuil: | |
| 200 | + borne_sup = min([s for s, _ in taux_municipal if s > seuil] + [float("inf")]) | |
| 201 | + supplement += (min(base, borne_sup) - seuil) * (taux - 0.015) | |
| 202 | + return {"base": base, "detail": detail, "droit_base": total, "supplement_municipal": supplement, "total": total + supplement, | |
| 203 | + "taux_effectif": (total + supplement) / base} | |
| 204 | + | |
| 205 | + | |
| 206 | +def quantum_vice(prix: float, cout_reparation: float, plus_value: float, prejudice_autre: float = 0.0, vendeur_connaissait: bool = False) -> dict: | |
| 207 | + """Mesure usuelle du préjudice d'un vice caché : coût des réparations diminué de la plus-value qu'elles procurent | |
| 208 | + (réduction de prix, art. 1726-1727) ; dommages-intérêts additionnels seulement si le vendeur connaissait le vice (art. 1728).""" | |
| 209 | + reduction = cout_reparation - plus_value | |
| 210 | + dommages = prejudice_autre if vendeur_connaissait else 0.0 | |
| 211 | + return {"prix": prix, "reduction_prix": reduction, "dommages": dommages, "total": reduction + dommages, | |
| 212 | + "prix_ajuste": prix - reduction, "part_prix": reduction / prix} | |
| 213 | + | |
| 214 | + | |
| 215 | +def budget_registre(nb_comparables: int, documents_par_vente: int = 3) -> dict: | |
| 216 | + """Budget de consultation du registre foncier pour vérifier des ventes comparables (1 $ par document).""" | |
| 217 | + n_doc = nb_comparables * documents_par_vente | |
| 218 | + return {"documents": n_doc, "cout": n_doc * TARIFS_REGISTRE["consultation_document"]} | |
| 219 | + | |
| 220 | + | |
| 221 | +# ----------------------------------------------------------------------------- impression | |
| 222 | + | |
| 223 | +def _d(x: float) -> str: | |
| 224 | + return f"{x:,.0f} $".replace(",", " ") | |
| 225 | + | |
| 226 | + | |
| 227 | +def main() -> None: | |
| 228 | + print("=== Chapitre 6 — démembrements et modalités (DONNÉES SYNTHÉTIQUES / hypothèses déclarées) ===\n") | |
| 229 | + tm = table_mortalite() | |
| 230 | + print("Table de mortalité SYNTHÉTIQUE (Gompertz–Makeham A=%.4f B=%.6f c=%.3f) :" % (GM_A, GM_B, GM_C)) | |
| 231 | + print(tm.round(3).to_string(), "\n") | |
| 232 | + | |
| 233 | + c1 = valeur_cas1() | |
| 234 | + V = c1["V"] | |
| 235 | + print(f"Cas 1 : prédiction hédonique {c1['prediction_hedonique']:.2f} $ → V retenu {_d(V)} ; L = (r-g)V = {_d(loyer_net(V))} / an " | |
| 236 | + f"({loyer_net(V)/12:.0f} $/mois) avec r = {R_ACTU:.1%}, g = {G_LOYER:.1%}\n") | |
| 237 | + | |
| 238 | + for n in (10, 20, 30): | |
| 239 | + u = usufruit_terme(V, n) | |
| 240 | + print(f"Usufruit à terme {n} ans : U = {_d(u['U'])} ; NP = {_d(u['NP'])} ({u['part_NP']:.1%}) ; vérif. NP formule {_d(u['NP_formule'])}") | |
| 241 | + print() | |
| 242 | + for x in (55, 65, 75, 85): | |
| 243 | + u = usufruit_viager(V, x) | |
| 244 | + print(f"Usufruit viager, {x} ans : e_x = {u['esperance_vie']:.1f} ; U = {_d(u['U'])} ; NP = {_d(u['NP'])} ({u['part_NP']:.1%}) ; " | |
| 245 | + f"NP direct = {_d(u['NP_direct'])} ; annuité viagère {u['annuite_viagere']:.3f} ; rente équiv. {_d(rente_viagere_equivalente(V, x))}/an") | |
| 246 | + print("\nGrille NP/V (âge × r) :"); print(grille_usufruit(V).round(3).to_string(), "\n") | |
| 247 | + | |
| 248 | + noi = rne_cas3() | |
| 249 | + e = emphyteose(noi, RENTE_EMPH, N_EMPH) | |
| 250 | + print(f"Emphytéose : RNE_1 cas 3 = {noi:.2f} $ ; r = {R_EMPH:.1%}, g = {G_EMPH:.1%} → V pleine = {_d(e['V_pleine'])} ; rente {_d(RENTE_EMPH)}/an ; n = {N_EMPH}") | |
| 251 | + print(f" droit de l'emphytéote {_d(e['V_emph'])} ({e['part_emph']:.1%}) ; propriétaire : rentes {_d(e['V_rentes'])} + reprise {_d(e['V_reprise'])} = {_d(e['V_prop'])} ; somme {_d(e['somme'])}") | |
| 252 | + e2 = emphyteose(noi, RENTE_EMPH, N_EMPH, r_prop=0.055) | |
| 253 | + print(f" avec r_prop = 5,5 % : propriétaire {_d(e2['V_prop'])} ; somme {_d(e2['somme'])} (> V pleine de {_d(e2['somme']-e2['V_pleine'])})") | |
| 254 | + print("\nGrille emphytéose selon la durée restante :") | |
| 255 | + ge = grille_emphyteose(noi, RENTE_EMPH)[["V_emph", "V_rentes", "V_reprise", "V_prop", "part_emph"]].copy() | |
| 256 | + ge["part_emph"] = (ge["part_emph"] * 100).round(1); ge[["V_emph", "V_rentes", "V_reprise", "V_prop"]] = ge[["V_emph", "V_rentes", "V_reprise", "V_prop"]].round(0) | |
| 257 | + print(ge.to_string(), "\n") | |
| 258 | + | |
| 259 | + for q, d in ((0.5, 0.0), (0.5, 0.10), (0.5, 0.20), (1 / 3, 0.15)): | |
| 260 | + p = part_indivise(V, q, d) | |
| 261 | + print(f"Part indivise {q:.3f} décote {d:.0%} : {_d(p['valeur_proportionnelle'])} → {_d(p['valeur_part'])}") | |
| 262 | + s = servitude_terrain(V, c1["terrain_m2"], 120, c1["elasticite_terrain"]) | |
| 263 | + print(f"\nServitude de non-construction sur 120 m² de {c1['terrain_m2']} m² (élasticité {s['elasticite']:.4f}) : V_avec = {_d(s['V_avec'])} ; ΔV = {_d(s['delta'])} ({s['delta_pct']:.2%})") | |
| 264 | + print("\nRang hypothécaire, produit net 560 000 $, créances 455 000 / 90 000 / 40 000 :") | |
| 265 | + for r in rang_hypothecaire(560_000, [455_000, 90_000, 40_000]): | |
| 266 | + print(" ", r) | |
| 267 | + m = droit_mutation(V) | |
| 268 | + print(f"\nDroit de mutation 2026 sur {_d(V)} : " + " ; ".join(f"{t['taux']:.1%} sur {_d(t['a']-t['de'])} = {t['montant']:.2f} $" for t in m["detail"]) + | |
| 269 | + f" → {m['total']:.2f} $ ({m['taux_effectif']:.3%})") | |
| 270 | + m2 = droit_mutation(750_000, taux_municipal=[(500_000, 0.02)]) | |
| 271 | + print(f"Droit de mutation 2026 sur 750 000 $ avec taux municipal 2 % au-delà de 500 000 $ : base {m2['droit_base']:.2f} + {m2['supplement_municipal']:.2f} = {m2['total']:.2f} $") | |
| 272 | + qv = quantum_vice(520_000, 46_000, 9_000, 6_500, True) | |
| 273 | + print(f"\nVice caché : prix 520 000 $, réparations 46 000 $, plus-value 9 000 $ → réduction {_d(qv['reduction_prix'])} ({qv['part_prix']:.1%}) ; " | |
| 274 | + f"si vendeur connaissait : + {_d(qv['dommages'])} = {_d(qv['total'])}") | |
| 275 | + b = budget_registre(5) | |
| 276 | + print(f"\nRegistre foncier : 5 comparables × 3 documents = {b['documents']} documents → {b['cout']:.0f} $ (1 $/document)") | |
| 277 | + | |
| 278 | + | |
| 279 | +if __name__ == "__main__": | |
| 280 | + main() | |
added
analysis/python/cases/ch07_servitudes_copro.py
+280 −0
@@ -0,0 +1,280 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Chapitre 7 — Cadastre, titres, servitudes et copropriété : exemples chiffrés (DONNÉES SYNTHÉTIQUES, graine 20261031). | |
| 3 | + | |
| 4 | +Trois blocs, tous générés dans ce script (aucun fichier externe, aucune statistique de marché) : | |
| 5 | + A. Effet d'une servitude de passage mesuré par ventes appariées (traite.comparables.ventes_appariees) ; | |
| 6 | + comparaison avec le « modèle de la bande » (perte d'utilité d'une bande de terrain grevée). | |
| 7 | + B. Copropriété divise de huit fractions : valeur relative (art. 1041, 1053 C.c.Q.), quotes-parts, répartition | |
| 8 | + des charges communes générales et des charges des parties communes à usage restreint (art. 1064 C.c.Q.). | |
| 9 | + C. Fonds de prévoyance (art. 1071 C.c.Q. ; CCQ, r. 8.01, art. 3 et 7-9) : plan de réparations majeures sur 25 ans, | |
| 10 | + cotisation annuelle indexée nécessaire (méthode du seuil), comparaison avec la règle du 0,5 % de la valeur de | |
| 11 | + reconstruction et avec la cotisation actuelle, déficit de capitalisation et incidence sur la valeur d'une fraction. | |
| 12 | + | |
| 13 | +Toutes les fonctions financières proviennent de analysis/python/traite/ (finance.py, comparables.py). | |
| 14 | +Usage (racine du dépôt) : PYTHONPATH=analysis/python python3 analysis/python/cases/ch07_servitudes_copro.py | |
| 15 | +""" | |
| 16 | +from __future__ import annotations | |
| 17 | +import sys, math, pathlib | |
| 18 | +import numpy as np | |
| 19 | +from scipy import stats | |
| 20 | +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1])) | |
| 21 | +from traite import comparables as cp | |
| 22 | +from traite import finance as fin | |
| 23 | + | |
| 24 | +SEED = 20261031 | |
| 25 | + | |
| 26 | +# ----------------------------------------------------------------------------- A. servitude par paires | |
| 27 | +N_PAIRES = 12 | |
| 28 | +PRIX_MEDIAN_SANS = 445_000.0 # $ ; niveau pédagogique (bungalow de banlieue fictif, terrain 700 m²) | |
| 29 | +SIGMA_PAIRE = 0.08 # dispersion log des prix « sans servitude » entre paires | |
| 30 | +DELTA_SERVITUDE = 0.045 # effet « vrai » de la servitude de passage (proportion du prix) — hypothèse pédagogique | |
| 31 | +SIGMA_BRUIT = 0.025 # bruit de transaction résiduel au sein d'une paire | |
| 32 | +TERRAIN_M2 = 700.0 | |
| 33 | +BANDE_LARGEUR_M, BANDE_LONGUEUR_M = 3.0, 40.0 # bande de passage grevée | |
| 34 | +PRIX_TERRAIN_M2 = 180.0 # $/m² de terrain, prix unitaire pédagogique du secteur fictif | |
| 35 | +PERTE_UTILITE = 0.50 # proportion de l'utilité de la bande perdue par le fonds servant (passage non exclusif) | |
| 36 | + | |
| 37 | + | |
| 38 | +def paires_servitude(n: int = N_PAIRES, seed: int = SEED) -> dict: | |
| 39 | + """Génère n paires (sans, avec servitude de passage) et extrait l'effet par ventes appariées.""" | |
| 40 | + rng = np.random.default_rng(seed) | |
| 41 | + sans = np.exp(np.log(PRIX_MEDIAN_SANS) + rng.normal(0.0, SIGMA_PAIRE, n)) | |
| 42 | + avec = sans * (1.0 - DELTA_SERVITUDE) * np.exp(rng.normal(0.0, SIGMA_BRUIT, n)) | |
| 43 | + sans = np.round(sans / 500.0) * 500.0 | |
| 44 | + avec = np.round(avec / 500.0) * 500.0 | |
| 45 | + va = cp.ventes_appariees(list(avec), list(sans)) # différences = avec − sans (négatives) | |
| 46 | + diffs = np.array(va["differences"]) | |
| 47 | + se = va["ecart_type"] / math.sqrt(n) | |
| 48 | + t_crit = float(stats.t.ppf(0.975, n - 1)) | |
| 49 | + ratios = avec / sans | |
| 50 | + beta = float(np.mean(np.log(ratios))) | |
| 51 | + return { | |
| 52 | + "n": n, "sans": sans, "avec": avec, "diffs": diffs, | |
| 53 | + "moyenne": va["moyenne"], "mediane": va["mediane"], "ecart_type": va["ecart_type"], "se": se, | |
| 54 | + "t": va["moyenne"] / se, "t_crit": t_crit, | |
| 55 | + "ic95": (va["moyenne"] - t_crit * se, va["moyenne"] + t_crit * se), | |
| 56 | + "effet_relatif_moyen": float(np.mean(ratios - 1.0)), # moyenne des (avec/sans − 1) | |
| 57 | + "effet_relatif_ratio": float(avec.mean() / sans.mean() - 1.0), # rapport des moyennes | |
| 58 | + "beta_log": beta, "effet_semilog": cp.ajustement_depuis_coefficient_log(beta), | |
| 59 | + "ic95_relatif": ((va["moyenne"] - t_crit * se) / sans.mean(), (va["moyenne"] + t_crit * se) / sans.mean()), | |
| 60 | + } | |
| 61 | + | |
| 62 | + | |
| 63 | +def modele_bande(prix_terrain_m2: float = PRIX_TERRAIN_M2, largeur: float = BANDE_LARGEUR_M, | |
| 64 | + longueur: float = BANDE_LONGUEUR_M, perte: float = PERTE_UTILITE) -> dict: | |
| 65 | + """Effet d'une servitude = superficie grevée × prix unitaire du terrain × proportion d'utilité perdue.""" | |
| 66 | + s = largeur * longueur | |
| 67 | + return {"superficie_bande_m2": s, "valeur_bande": s * prix_terrain_m2, "effet": s * prix_terrain_m2 * perte, | |
| 68 | + "part_terrain": s / TERRAIN_M2} | |
| 69 | + | |
| 70 | + | |
| 71 | +# ----------------------------------------------------------------------------- B. copropriété : quotes-parts et charges | |
| 72 | +FRACTIONS = [ # (nom, superficie privative m², étage, orientation, stationnement intérieur à usage restreint) | |
| 73 | + ("F1", 72.0, "RDC", "nord", False), ("F2", 98.0, "RDC", "sud", True), | |
| 74 | + ("F3", 72.0, "2e", "nord", False), ("F4", 98.0, "2e", "sud", True), | |
| 75 | + ("F5", 85.0, "3e", "nord", False), ("F6", 110.0, "3e", "sud", True), | |
| 76 | + ("F7", 85.0, "2e", "est", True), ("F8", 90.0, "3e", "ouest", False), | |
| 77 | +] | |
| 78 | +COEF_ETAGE = {"RDC": 0.96, "2e": 1.00, "3e": 1.04} # méthode déclarée dans l'acte constitutif (fictif) | |
| 79 | +COEF_ORIENTATION = {"nord": 0.97, "est": 1.00, "ouest": 1.00, "sud": 1.03} | |
| 80 | +BASE_QUOTE = 10_000 # quotes-parts exprimées en dix-millièmes | |
| 81 | + | |
| 82 | +CHARGES_COURANTES = { # budget annuel des charges communes générales, hors fonds (dollars de 2026, fictif) | |
| 83 | + "Assurance de l'immeuble": 12_000.0, "Entretien et réparations courantes": 8_500.0, | |
| 84 | + "Déneigement et entretien paysager": 5_200.0, "Énergie des parties communes": 4_800.0, | |
| 85 | + "Gestion et honoraires professionnels": 7_200.0, "Administration et frais divers": 2_300.0, | |
| 86 | +} | |
| 87 | +FRANCHISE_MAX = 10_000.0 # plus haute franchise de la police du syndicat (dégâts d'eau) — fictif | |
| 88 | +FONDS_AUTO_ASSURANCE_SOLDE = 0.0 # fonds d'auto assurance non capitalisé → contribution = moitié de la franchise (r. 4.1, art. 2) | |
| 89 | +ENTRETIEN_STATIONNEMENTS = 3_200.0 # entretien courant des 4 stationnements intérieurs (partie commune à usage restreint) | |
| 90 | + | |
| 91 | + | |
| 92 | +def quotes_parts(fractions=FRACTIONS) -> dict: | |
| 93 | + """Valeur relative v_i = S_i × c_étage × c_orientation ; quote-part q_i = v_i / Σ v, en dix-millièmes (somme exacte = 10 000).""" | |
| 94 | + v = np.array([s * COEF_ETAGE[e] * COEF_ORIENTATION[o] for _, s, e, o, _ in fractions]) | |
| 95 | + q = v / v.sum() | |
| 96 | + brut = q * BASE_QUOTE | |
| 97 | + ent = np.floor(brut).astype(int) | |
| 98 | + reste = BASE_QUOTE - ent.sum() | |
| 99 | + ordre = np.argsort(-(brut - ent)) # plus forts restes | |
| 100 | + ent[ordre[:reste]] += 1 | |
| 101 | + return {"noms": [f[0] for f in fractions], "superficies": np.array([f[1] for f in fractions]), | |
| 102 | + "valeur_relative": v, "q": ent / BASE_QUOTE, "q_dixmilliemes": ent, "q_brut": q, | |
| 103 | + "stationnement": np.array([f[4] for f in fractions])} | |
| 104 | + | |
| 105 | + | |
| 106 | +def contribution_auto_assurance(franchise_max: float = FRANCHISE_MAX, solde: float = FONDS_AUTO_ASSURANCE_SOLDE) -> float: | |
| 107 | + """CCQ, r. 4.1, art. 2 : moitié de la plus haute franchise si le fonds est capitalisé à moins de la moitié ; différence | |
| 108 | + entre la franchise et le solde si entre la moitié et la franchise ; nulle au-delà (franchises séisme/inondation exclues).""" | |
| 109 | + if solde <= franchise_max / 2: | |
| 110 | + return franchise_max / 2 | |
| 111 | + if solde < franchise_max: | |
| 112 | + return franchise_max - solde | |
| 113 | + return 0.0 | |
| 114 | + | |
| 115 | + | |
| 116 | +def repartition_charges(cotisation_fonds_prevoyance: float, qp: dict | None = None) -> dict: | |
| 117 | + """Charges annuelles de chaque fraction : (courantes + fonds de prévoyance + fonds d'auto assurance) × q_i | |
| 118 | + + part égale de l'entretien des stationnements pour leurs seuls usagers (art. 1064 C.c.Q.).""" | |
| 119 | + qp = qp or quotes_parts() | |
| 120 | + courantes = sum(CHARGES_COURANTES.values()) | |
| 121 | + auto = contribution_auto_assurance() | |
| 122 | + general = courantes + cotisation_fonds_prevoyance + auto | |
| 123 | + n_stat = int(qp["stationnement"].sum()) | |
| 124 | + part_stat = np.where(qp["stationnement"], ENTRETIEN_STATIONNEMENTS / n_stat, 0.0) | |
| 125 | + q = qp["q"] | |
| 126 | + return {"courantes": courantes, "fonds_prevoyance": cotisation_fonds_prevoyance, "auto_assurance": auto, "general": general, | |
| 127 | + "par_fraction_courantes": q * courantes, "par_fraction_fonds": q * cotisation_fonds_prevoyance, | |
| 128 | + "par_fraction_auto": q * auto, "par_fraction_general": q * general, "par_fraction_restreint": part_stat, | |
| 129 | + "par_fraction_total": q * general + part_stat, "total": general + ENTRETIEN_STATIONNEMENTS, "n_stationnements": n_stat} | |
| 130 | + | |
| 131 | + | |
| 132 | +# ----------------------------------------------------------------------------- C. fonds de prévoyance | |
| 133 | +VALEUR_RECONSTRUCTION = 4_200_000.0 # $ ; montant d'assurance (art. 1073 C.c.Q.), fixé par un évaluateur agréé (r. 4.1, art. 3) — fictif | |
| 134 | +HORIZON = 25 # années (CCQ, r. 8.01, art. 3 : « minimalement les 25 prochaines années ») | |
| 135 | +INFLATION = 0.025 # hausse annuelle des coûts de construction retenue dans l'étude (hypothèse pédagogique) | |
| 136 | +RENDEMENT = 0.020 # rendement des placements à capital garanti (art. 1071 : « placé … à capital garanti ») | |
| 137 | +SOLDE_INITIAL = 85_000.0 # solde du fonds de prévoyance au début de l'année 1 (2026) — fictif | |
| 138 | +COTISATION_ACTUELLE = 12_000.0 # cotisation annuelle votée jusqu'ici (fictif) — insuffisante, on le verra | |
| 139 | +COMPOSANTES = [ # (composante, coût en dollars de 2026, années prévues 1..25) | |
| 140 | + ("Peinture et éclairage des corridors", 18_000.0, (4, 13, 22)), | |
| 141 | + ("Maçonnerie et balcons", 55_000.0, (5,)), | |
| 142 | + ("Toiture (membrane)", 75_000.0, (7,)), | |
| 143 | + ("Pavage et drainage du stationnement", 28_000.0, (9, 21)), | |
| 144 | + ("Chauffage et ventilation des parties communes", 42_000.0, (12,)), | |
| 145 | + ("Fenêtres et portes-fenêtres", 140_000.0, (14,)), | |
| 146 | + ("Portes de garage et équipements du stationnement", 22_000.0, (16,)), | |
| 147 | + ("Entrée d'eau et drains", 35_000.0, (19,)), | |
| 148 | +] | |
| 149 | + | |
| 150 | + | |
| 151 | +def depenses_prevues(composantes=COMPOSANTES, horizon: int = HORIZON, inflation: float = INFLATION) -> np.ndarray: | |
| 152 | + """D_t, t = 1..horizon : coûts indexés C_0 (1+π)^t des réparations majeures prévues l'année t.""" | |
| 153 | + d = np.zeros(horizon + 1) | |
| 154 | + for _, c0, annees in composantes: | |
| 155 | + for t in annees: | |
| 156 | + if 1 <= t <= horizon: | |
| 157 | + d[t] += c0 * (1.0 + inflation) ** t | |
| 158 | + return d | |
| 159 | + | |
| 160 | + | |
| 161 | +def trajectoire(cotisation_an1: float, solde0: float = SOLDE_INITIAL, rendement: float = RENDEMENT, inflation: float = INFLATION, | |
| 162 | + horizon: int = HORIZON, composantes=COMPOSANTES, indexer: bool = True) -> dict: | |
| 163 | + """F_t = F_{t-1}(1+r) + A_t − D_t, avec A_t = A_1 (1+π)^{t−1} si indexée (convention de l'étude), flux en fin d'année.""" | |
| 164 | + d = depenses_prevues(composantes, horizon, inflation) | |
| 165 | + F = np.zeros(horizon + 1); F[0] = solde0 | |
| 166 | + A = np.zeros(horizon + 1) | |
| 167 | + for t in range(1, horizon + 1): | |
| 168 | + A[t] = cotisation_an1 * ((1.0 + inflation) ** (t - 1) if indexer else 1.0) | |
| 169 | + F[t] = F[t - 1] * (1.0 + rendement) + A[t] - d[t] | |
| 170 | + return {"F": F, "A": A, "D": d, "min": float(F[1:].min()), "annee_min": int(np.argmin(F[1:]) + 1), | |
| 171 | + "premiere_annee_negative": next((t for t in range(1, horizon + 1) if F[t] < 0), None)} | |
| 172 | + | |
| 173 | + | |
| 174 | +def cotisation_seuil(plancher: float = 0.0, solde0: float = SOLDE_INITIAL, rendement: float = RENDEMENT, inflation: float = INFLATION, | |
| 175 | + horizon: int = HORIZON, composantes=COMPOSANTES, indexer: bool = True) -> float: | |
| 176 | + """Méthode du seuil : plus petite cotisation A_1 (indexée) telle que le solde ne descende jamais sous `plancher`.""" | |
| 177 | + lo, hi = 0.0, 500_000.0 | |
| 178 | + for _ in range(200): | |
| 179 | + mid = 0.5 * (lo + hi) | |
| 180 | + if trajectoire(mid, solde0, rendement, inflation, horizon, composantes, indexer)["min"] >= plancher: | |
| 181 | + hi = mid | |
| 182 | + else: | |
| 183 | + lo = mid | |
| 184 | + return hi | |
| 185 | + | |
| 186 | + | |
| 187 | +def solde_initial_requis(cotisation: float = COTISATION_ACTUELLE, plancher: float = 0.0, **kw) -> float: | |
| 188 | + """Solde d'ouverture qui rendrait la cotisation actuelle suffisante (sert à mesurer le déficit de capitalisation).""" | |
| 189 | + lo, hi = 0.0, 5_000_000.0 | |
| 190 | + for _ in range(200): | |
| 191 | + mid = 0.5 * (lo + hi) | |
| 192 | + if trajectoire(cotisation, mid, **kw)["min"] >= plancher: | |
| 193 | + hi = mid | |
| 194 | + else: | |
| 195 | + lo = mid | |
| 196 | + return hi | |
| 197 | + | |
| 198 | + | |
| 199 | +def fonds_composante_unique(cout_actuel: float, annees: int, inflation: float = INFLATION, rendement: float = RENDEMENT) -> dict: | |
| 200 | + """Vérification par le facteur de fonds d'amortissement : versement constant qui accumule C_0 (1+π)^T en T ans au taux r.""" | |
| 201 | + cT = cout_actuel * (1.0 + inflation) ** annees | |
| 202 | + sff = fin.facteur_fonds_amortissement(rendement, annees) | |
| 203 | + return {"cout_indexe": cT, "sff": sff, "versement": cT * sff} | |
| 204 | + | |
| 205 | + | |
| 206 | +def sensibilite_cotisation(inflations=(0.015, 0.020, 0.025, 0.030, 0.035), rendements=(0.010, 0.020, 0.030), plancher: float = 0.0) -> dict: | |
| 207 | + grille = {r: [cotisation_seuil(plancher, inflation=p, rendement=r) for p in inflations] for r in rendements} | |
| 208 | + return {"inflations": list(inflations), "rendements": list(rendements), "grille": grille} | |
| 209 | + | |
| 210 | + | |
| 211 | +def incidence_valeur(qp: dict | None = None, cotisation: float = COTISATION_ACTUELLE) -> dict: | |
| 212 | + """Déficit de capitalisation D_0 = solde requis − solde réel ; ajustement de la fraction i = −q_i × D_0 (hypothèse : capitalisation 1 pour 1).""" | |
| 213 | + qp = qp or quotes_parts() | |
| 214 | + requis = solde_initial_requis(cotisation) | |
| 215 | + deficit = requis - SOLDE_INITIAL | |
| 216 | + return {"solde_requis": requis, "deficit": deficit, "ajustement_par_fraction": -qp["q"] * deficit} | |
| 217 | + | |
| 218 | + | |
| 219 | +# ----------------------------------------------------------------------------- impression | |
| 220 | +def fmt(x): return f"{x:,.0f}".replace(",", " ") | |
| 221 | + | |
| 222 | + | |
| 223 | +def main() -> dict: | |
| 224 | + out = {} | |
| 225 | + # A | |
| 226 | + p = paires_servitude(); out["paires"] = p | |
| 227 | + print("== A. Servitude de passage — ventes appariées (n = %d, graine %d)" % (p["n"], SEED)) | |
| 228 | + for i, (s, a, d) in enumerate(zip(p["sans"], p["avec"], p["diffs"]), 1): | |
| 229 | + print(f" paire {i:2d} : sans {fmt(s):>9} $ avec {fmt(a):>9} $ diff {d:>9,.0f} $ ratio {a/s-1:+.3%}") | |
| 230 | + print(f" moyenne {p['moyenne']:,.0f} $ ; médiane {p['mediane']:,.0f} $ ; écart-type {p['ecart_type']:,.0f} $ ; erreur-type {p['se']:,.0f} $") | |
| 231 | + print(f" t = {p['t']:.2f} (t crit. {p['t_crit']:.3f}) ; IC 95 % [{p['ic95'][0]:,.0f} ; {p['ic95'][1]:,.0f}] $") | |
| 232 | + print(f" effet relatif : moyenne des ratios {p['effet_relatif_moyen']:.4%} ; rapport des moyennes {p['effet_relatif_ratio']:.4%} ; semi-log exp(β)−1 = {p['effet_semilog']:.4%} (β = {p['beta_log']:.5f})") | |
| 233 | + print(f" IC 95 % relatif [{p['ic95_relatif'][0]:.2%} ; {p['ic95_relatif'][1]:.2%}] ; moyenne des prix sans {p['sans'].mean():,.0f} $") | |
| 234 | + b = modele_bande(); out["bande"] = b | |
| 235 | + print(f" modèle de la bande : {b['superficie_bande_m2']:.0f} m² × {PRIX_TERRAIN_M2:.0f} $/m² = {b['valeur_bande']:,.0f} $ × {PERTE_UTILITE:.0%} = {b['effet']:,.0f} $ ({b['part_terrain']:.1%} du terrain)") | |
| 236 | + # B | |
| 237 | + qp = quotes_parts(); out["qp"] = qp | |
| 238 | + print("\n== B. Copropriété divise — valeur relative et quotes-parts (dix-millièmes)") | |
| 239 | + for n, s, v, q in zip(qp["noms"], qp["superficies"], qp["valeur_relative"], qp["q_dixmilliemes"]): | |
| 240 | + print(f" {n} : {s:5.0f} m² v = {v:8.2f} q = {q:5d}/10 000") | |
| 241 | + print(f" Σ v = {qp['valeur_relative'].sum():.2f} ; Σ q = {qp['q_dixmilliemes'].sum()} ; Σ S = {qp['superficies'].sum():.0f} m²") | |
| 242 | + # C | |
| 243 | + A_star = cotisation_seuil(); out["A_star"] = A_star | |
| 244 | + A_floor = cotisation_seuil(plancher=25_000.0); out["A_floor"] = A_floor | |
| 245 | + regle = 0.005 * VALEUR_RECONSTRUCTION; out["regle_0_5"] = regle | |
| 246 | + tr_act = trajectoire(COTISATION_ACTUELLE); tr_star = trajectoire(A_star); tr_regle = trajectoire(regle) | |
| 247 | + out.update({"tr_act": tr_act, "tr_star": tr_star, "tr_regle": tr_regle}) | |
| 248 | + D = tr_star["D"] | |
| 249 | + print("\n== C. Fonds de prévoyance — plan sur %d ans, π = %.1f %%, r = %.1f %%, F0 = %s $" % (HORIZON, INFLATION * 100, RENDEMENT * 100, fmt(SOLDE_INITIAL))) | |
| 250 | + tot0 = 0.0 | |
| 251 | + for nom, c0, annees in COMPOSANTES: | |
| 252 | + for t in annees: | |
| 253 | + print(f" an {t:2d} ({2026 + t - 1}) : {nom:48s} {fmt(c0):>8} $ → indexé {fmt(c0 * (1 + INFLATION) ** t):>8} $") | |
| 254 | + tot0 += c0 | |
| 255 | + print(f" total des dépenses prévues : {fmt(tot0)} $ (dollars de 2026) ; {fmt(D.sum())} $ (dollars courants)") | |
| 256 | + print(f" cotisation actuelle {fmt(COTISATION_ACTUELLE)} $ : solde minimal {tr_act['min']:,.0f} $ (an {tr_act['annee_min']}), premier solde négatif an {tr_act['premiere_annee_negative']} ; solde an 25 {tr_act['F'][-1]:,.0f} $") | |
| 257 | + print(f" règle 0,5 % × {fmt(VALEUR_RECONSTRUCTION)} = {fmt(regle)} $ : solde minimal {tr_regle['min']:,.0f} $ (an {tr_regle['annee_min']}), premier négatif an {tr_regle['premiere_annee_negative']}") | |
| 258 | + print(f" cotisation de seuil A* (plancher 0) = {A_star:,.0f} $ (an 1, indexée à {INFLATION:.1%}) ; solde minimal {tr_star['min']:,.2f} $ an {tr_star['annee_min']} ; solde an 25 {tr_star['F'][-1]:,.0f} $ ; an 25 cotisation {tr_star['A'][-1]:,.0f} $") | |
| 259 | + print(f" cotisation de seuil avec plancher 25 000 $ = {A_floor:,.0f} $") | |
| 260 | + fc = fonds_composante_unique(75_000.0, 7); out["toiture"] = fc | |
| 261 | + print(f" vérification toiture seule : {fmt(75000)} $ × 1,025^7 = {fc['cout_indexe']:,.0f} $ ; SFF(2 %, 7) = {fc['sff']:.5f} ; versement {fc['versement']:,.0f} $") | |
| 262 | + rep = repartition_charges(A_star, qp); out["rep"] = rep | |
| 263 | + print(f"\n== B'. Répartition des charges avec A* : courantes {fmt(rep['courantes'])} $ + fonds {fmt(rep['fonds_prevoyance'])} $ + auto-assurance {fmt(rep['auto_assurance'])} $ = {fmt(rep['general'])} $ ; stationnements {fmt(ENTRETIEN_STATIONNEMENTS)} $ / {rep['n_stationnements']}") | |
| 264 | + for i, n in enumerate(qp["noms"]): | |
| 265 | + print(f" {n} : q {qp['q'][i]:.4f} courantes {rep['par_fraction_courantes'][i]:8,.0f} fonds {rep['par_fraction_fonds'][i]:8,.0f} auto {rep['par_fraction_auto'][i]:6,.0f} restreint {rep['par_fraction_restreint'][i]:6,.0f} total {rep['par_fraction_total'][i]:8,.0f} $/an = {rep['par_fraction_total'][i]/12:6,.0f} $/mois") | |
| 266 | + print(f" total {rep['total']:,.0f} $ ; contrôle Σ = {rep['par_fraction_total'].sum():,.2f} $") | |
| 267 | + sens = sensibilite_cotisation(); out["sens"] = sens | |
| 268 | + print("\n== Sensibilité de A* (plancher 0) : lignes r, colonnes π") | |
| 269 | + print(" " + " ".join(f"{p:.1%}" for p in sens["inflations"])) | |
| 270 | + for r, row in sens["grille"].items(): | |
| 271 | + print(f" r={r:.1%} " + " ".join(f"{a:7,.0f}" for a in row)) | |
| 272 | + inc = incidence_valeur(qp); out["incidence"] = inc | |
| 273 | + print(f"\n== Incidence sur la valeur : solde requis avec {fmt(COTISATION_ACTUELLE)} $ = {inc['solde_requis']:,.0f} $ ; déficit {inc['deficit']:,.0f} $") | |
| 274 | + for n, a in zip(qp["noms"], inc["ajustement_par_fraction"]): | |
| 275 | + print(f" {n} : ajustement {a:,.0f} $") | |
| 276 | + return out | |
| 277 | + | |
| 278 | + | |
| 279 | +if __name__ == "__main__": | |
| 280 | + main() | |
added
analysis/python/cases/ch08_zonage.py
+351 −0
@@ -0,0 +1,351 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Chapitre 8 — Zonage et urbanisme : DONNÉES SYNTHÉTIQUES et calculs de l'exemple intégral. | |
| 3 | + | |
| 4 | +Tout est fictif : la « municipalité de Val-Fictif » est une grille de 20 × 20 lots répartie en cinq zones | |
| 5 | +(H1 unifamiliale, H2 2 à 4 logements, H3 multifamiliale, C mixte, P parc/institutionnel). Un îlot de 36 lots | |
| 6 | +de la zone H1 est rezoné H2 le 1er janvier 2025 (T0). Les ventes (une ou deux par lot résidentiel) suivent | |
| 7 | +une loi déclarée ci-dessous ; l'effet du rezonage y est injecté sous la forme d'une prime croissante avec | |
| 8 | +l'âge de la maison (option de redéveloppement : plus la maison est vieille, plus le terrain domine la valeur). | |
| 9 | + | |
| 10 | +Le script imprime tous les chiffres cités dans manuscript/chapters/ch08.tex et écrit, s'il y a lieu, | |
| 11 | +data/synthetic/ch08_zonage_lots.csv et data/synthetic/ch08_zonage_ventes.csv (graine 20261031). | |
| 12 | + | |
| 13 | +Usage : python3 analysis/python/cases/ch08_zonage.py | |
| 14 | +""" | |
| 15 | +from __future__ import annotations | |
| 16 | +import sys, pathlib, math, json | |
| 17 | +from datetime import date, timedelta | |
| 18 | +import numpy as np | |
| 19 | +import pandas as pd | |
| 20 | + | |
| 21 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 22 | +sys.path.insert(0, str(ROOT / "analysis/python")) | |
| 23 | +from traite import comparables as cp # noqa: E402 | |
| 24 | + | |
| 25 | +SEED = 20261031 | |
| 26 | +T0 = date(2025, 1, 1) # entrée en vigueur du rezonage H1 → H2 | |
| 27 | +DATE_DEBUT, DATE_FIN = date(2023, 1, 1), date(2026, 8, 31) | |
| 28 | +DATE_REF = date(2024, 7, 1) # référence de l'indice temporel | |
| 29 | +TAUX_MARCHE = 0.03 # croissance annuelle composée des prix (hypothèse) | |
| 30 | +ELAST_HAB, ELAST_TERR = 0.50, 0.15 # élasticités prix/superficie (hypothèses pédagogiques, cf. cas 1 : 0,55 / 0,10) | |
| 31 | +BETA_AGE = -0.004 # effet de l'âge sur ln P (par année) | |
| 32 | +SIGMA = 0.07 # bruit transactionnel (écart-type de ln P) | |
| 33 | +PRIX_BASE = {"H1": 430000.0, "H2": 640000.0, "H3": 1150000.0} # prix de référence par zone (bâtiment type) | |
| 34 | +NCOL = NLIG = 20 | |
| 35 | + | |
| 36 | +# ----------------------------------------------------------------------------- lots et zones | |
| 37 | + | |
| 38 | +def zone_du_lot(x: int, y: int) -> str: | |
| 39 | + if y < 3: | |
| 40 | + return "C" | |
| 41 | + if 3 <= y < 8 and x < 4: | |
| 42 | + return "P" | |
| 43 | + if y >= 8 and 10 <= x < 15: | |
| 44 | + return "H2" | |
| 45 | + if y >= 8 and x >= 15: | |
| 46 | + return "H3" | |
| 47 | + return "H1" | |
| 48 | + | |
| 49 | + | |
| 50 | +def est_rezone(x: int, y: int) -> bool: | |
| 51 | + return y >= 8 and 7 <= x < 10 | |
| 52 | + | |
| 53 | + | |
| 54 | +def generer_lots(seed: int = SEED) -> pd.DataFrame: | |
| 55 | + rng = np.random.default_rng(seed) | |
| 56 | + rows = [] | |
| 57 | + for y in range(NLIG): | |
| 58 | + for x in range(NCOL): | |
| 59 | + z = zone_du_lot(x, y) | |
| 60 | + front = 20.0 if z in ("H1", "P") else (24.0 if z == "H2" else (30.0 if z == "H3" else 25.0)) | |
| 61 | + prof = 30.0 if z != "H3" else 36.0 | |
| 62 | + s_terr = round(front * prof * rng.uniform(0.92, 1.12), 0) | |
| 63 | + if z == "H1": | |
| 64 | + age = int(rng.integers(10, 76)) # maisons de 1951 à 2016 | |
| 65 | + s_hab = round(float(rng.normal(135, 22)), 0) | |
| 66 | + s_hab = float(min(max(s_hab, 85), 210)) | |
| 67 | + nlog = 1 | |
| 68 | + elif z == "H2": | |
| 69 | + age = int(rng.integers(5, 71)); s_hab = round(float(rng.normal(230, 35)), 0); nlog = int(rng.integers(2, 5)) | |
| 70 | + elif z == "H3": | |
| 71 | + age = int(rng.integers(5, 61)); s_hab = round(float(rng.normal(520, 80)), 0); nlog = int(rng.integers(4, 9)) | |
| 72 | + else: | |
| 73 | + age = int(rng.integers(5, 61)); s_hab = float("nan"); nlog = 0 | |
| 74 | + rows.append({"id_lot": f"L{y:02d}{x:02d}", "x": x, "y": y, "zone_initiale": z, | |
| 75 | + "zone_apres_T0": "H2" if (z == "H1" and est_rezone(x, y)) else z, | |
| 76 | + "rezone": bool(z == "H1" and est_rezone(x, y)), "superficie_terrain_m2": s_terr, | |
| 77 | + "superficie_habitable_m2": s_hab, "age_2026": age, "logements": nlog}) | |
| 78 | + return pd.DataFrame(rows) | |
| 79 | + | |
| 80 | + | |
| 81 | +# ----------------------------------------------------------------------------- ventes | |
| 82 | + | |
| 83 | +def prime_rezonage(age: float) -> float: | |
| 84 | + """Prime injectée (en ln P) pour une maison de l'îlot rezoné vendue après T0 : 0,03 + 0,0015 × max(âge − 20, 0).""" | |
| 85 | + return 0.03 + 0.0015 * max(age - 20.0, 0.0) | |
| 86 | + | |
| 87 | + | |
| 88 | +def _indice(d: date) -> float: | |
| 89 | + return (1.0 + TAUX_MARCHE) ** ((d - DATE_REF).days / 365.25) | |
| 90 | + | |
| 91 | + | |
| 92 | +def generer_ventes(lots: pd.DataFrame, seed: int = SEED) -> pd.DataFrame: | |
| 93 | + rng = np.random.default_rng(seed + 1) | |
| 94 | + res = lots[lots["zone_initiale"].isin(["H1", "H2", "H3"])].reset_index(drop=True) | |
| 95 | + n_jours = (DATE_FIN - DATE_DEBUT).days | |
| 96 | + rows = [] | |
| 97 | + for _, L in res.iterrows(): | |
| 98 | + n_ventes = 1 + int(rng.random() < 0.5) | |
| 99 | + jours = sorted(rng.integers(0, n_jours + 1, size=n_ventes).tolist()) | |
| 100 | + for k, j in enumerate(jours): | |
| 101 | + d = DATE_DEBUT + timedelta(days=int(j)) | |
| 102 | + age_vente = L["age_2026"] - (2026 - d.year) | |
| 103 | + z = L["zone_initiale"] | |
| 104 | + lnP = (math.log(PRIX_BASE[z]) + ELAST_HAB * math.log(L["superficie_habitable_m2"] / (135.0 if z == "H1" else 230.0 if z == "H2" else 520.0)) | |
| 105 | + + ELAST_TERR * math.log(L["superficie_terrain_m2"] / 600.0) + BETA_AGE * age_vente + math.log(_indice(d))) | |
| 106 | + traite_post = bool(L["rezone"] and d >= T0) | |
| 107 | + if traite_post: | |
| 108 | + lnP += prime_rezonage(age_vente) | |
| 109 | + lnP += rng.normal(0.0, SIGMA) | |
| 110 | + prix = round(math.exp(lnP) / 500.0) * 500.0 | |
| 111 | + rows.append({"id_vente": f"V{len(rows)+1:04d}", "id_lot": L["id_lot"], "zone_initiale": z, "rezone": bool(L["rezone"]), | |
| 112 | + "date_vente": d.isoformat(), "post_T0": d >= T0, "traite_post": traite_post, | |
| 113 | + "superficie_habitable_m2": L["superficie_habitable_m2"], "superficie_terrain_m2": L["superficie_terrain_m2"], | |
| 114 | + "age_vente": int(age_vente), "prix_vente": prix}) | |
| 115 | + return pd.DataFrame(rows) | |
| 116 | + | |
| 117 | + | |
| 118 | +# ----------------------------------------------------------------------------- analyse : paires et DiD | |
| 119 | + | |
| 120 | +def _mois(d1: date, d2: date) -> float: | |
| 121 | + return (d1 - d2).days / 30.4375 | |
| 122 | + | |
| 123 | + | |
| 124 | +def apparier(ventes: pd.DataFrame, date_eval: date = date(2026, 9, 1)) -> pd.DataFrame: | |
| 125 | + """Chaque vente traitée (îlot rezoné, après T0) est appariée à la vente témoin H1 non rezonée, postérieure à T0, | |
| 126 | + la plus proche (superficies, âge, date), sans remise. Le prix du témoin est ajusté au temps, à la superficie | |
| 127 | + habitable, au terrain et à l'âge avec les coefficients déclarés ; la différence est exprimée en % du témoin ajusté.""" | |
| 128 | + tr = ventes[ventes["traite_post"]].copy() | |
| 129 | + ctl = ventes[(ventes["zone_initiale"] == "H1") & (~ventes["rezone"]) & (ventes["post_T0"])].copy() | |
| 130 | + utilises: set[str] = set(); paires = [] | |
| 131 | + for _, t in tr.sort_values("date_vente").iterrows(): | |
| 132 | + dt = date.fromisoformat(t["date_vente"]) | |
| 133 | + cand = ctl[~ctl["id_vente"].isin(utilises)] | |
| 134 | + d = np.sqrt(((cand["superficie_habitable_m2"] - t["superficie_habitable_m2"]) / 15.0) ** 2 | |
| 135 | + + ((cand["superficie_terrain_m2"] - t["superficie_terrain_m2"]) / 80.0) ** 2 | |
| 136 | + + ((cand["age_vente"] - t["age_vente"]) / 8.0) ** 2 | |
| 137 | + + (np.array([_mois(dt, date.fromisoformat(s)) for s in cand["date_vente"]]) / 4.0) ** 2) | |
| 138 | + c = cand.loc[d.idxmin()]; utilises.add(c["id_vente"]) | |
| 139 | + dc = date.fromisoformat(c["date_vente"]) | |
| 140 | + # ajustements du témoin vers le traité (point de vue du traité) : temps, superficies, âge | |
| 141 | + a_temps = cp.ajustement_temps_taux(c["prix_vente"], TAUX_MARCHE, _mois(dt, dc)) | |
| 142 | + p1 = c["prix_vente"] + a_temps | |
| 143 | + fac = ((t["superficie_habitable_m2"] / c["superficie_habitable_m2"]) ** ELAST_HAB | |
| 144 | + * (t["superficie_terrain_m2"] / c["superficie_terrain_m2"]) ** ELAST_TERR | |
| 145 | + * math.exp(BETA_AGE * (t["age_vente"] - c["age_vente"]))) | |
| 146 | + p_adj = p1 * fac | |
| 147 | + paires.append({"id_traite": t["id_vente"], "id_temoin": c["id_vente"], "date_traite": t["date_vente"], "date_temoin": c["date_vente"], | |
| 148 | + "age_traite": int(t["age_vente"]), "age_temoin": int(c["age_vente"]), "distance": float(d.min()), | |
| 149 | + "prix_traite": t["prix_vente"], "prix_temoin": c["prix_vente"], "ajust_temps": a_temps, | |
| 150 | + "facteur_physique": fac, "prix_temoin_ajuste": p_adj, "difference": t["prix_vente"] - p_adj, | |
| 151 | + "difference_pct": (t["prix_vente"] - p_adj) / p_adj}) | |
| 152 | + return pd.DataFrame(paires) | |
| 153 | + | |
| 154 | + | |
| 155 | +def resume_paires(paires: pd.DataFrame) -> dict: | |
| 156 | + r = cp.ventes_appariees(paires["prix_traite"].tolist(), paires["prix_temoin_ajuste"].tolist()) | |
| 157 | + pct = paires["difference_pct"].to_numpy() | |
| 158 | + jeunes = paires[paires["age_traite"] < 40]; vieux = paires[paires["age_traite"] >= 40] | |
| 159 | + return {"n": r["n"], "moyenne": r["moyenne"], "mediane": r["mediane"], "ecart_type": r["ecart_type"], | |
| 160 | + "erreur_type_moyenne": r["ecart_type"] / math.sqrt(r["n"]), "moyenne_pct": float(pct.mean()), "mediane_pct": float(np.median(pct)), | |
| 161 | + "ecart_type_pct": float(pct.std(ddof=1)), "ic95_pct": (float(pct.mean() - 1.96 * pct.std(ddof=1) / math.sqrt(len(pct))), | |
| 162 | + float(pct.mean() + 1.96 * pct.std(ddof=1) / math.sqrt(len(pct)))), | |
| 163 | + "n_jeunes": len(jeunes), "moyenne_pct_jeunes": float(jeunes["difference_pct"].mean()), | |
| 164 | + "n_vieux": len(vieux), "moyenne_pct_vieux": float(vieux["difference_pct"].mean()), | |
| 165 | + "pente_age": float(np.polyfit(paires["age_traite"], pct, 1)[0])} | |
| 166 | + | |
| 167 | + | |
| 168 | +def did(ventes: pd.DataFrame) -> dict: | |
| 169 | + """Doubles différences sur les ventes H1 (îlot rezoné = traité ; reste de H1 = témoin). (a) 2 × 2 sur ln P ; | |
| 170 | + (b) régression avec superficies, âge et tendance : ln P = a + b·traité + c·post + δ·traité×post + x'γ + ε.""" | |
| 171 | + h1 = ventes[ventes["zone_initiale"] == "H1"].copy() | |
| 172 | + h1["lnP"] = np.log(h1["prix_vente"]) | |
| 173 | + g = h1.groupby(["rezone", "post_T0"])["lnP"].agg(["mean", "count"]) | |
| 174 | + m = {(bool(i), bool(j)): float(g.loc[(i, j), "mean"]) for i, j in g.index} | |
| 175 | + n = {(bool(i), bool(j)): int(g.loc[(i, j), "count"]) for i, j in g.index} | |
| 176 | + delta_2x2 = (m[(True, True)] - m[(True, False)]) - (m[(False, True)] - m[(False, False)]) | |
| 177 | + t_annees = np.array([(date.fromisoformat(s) - DATE_REF).days / 365.25 for s in h1["date_vente"]]) | |
| 178 | + X = np.column_stack([np.ones(len(h1)), h1["rezone"].astype(float), h1["post_T0"].astype(float), | |
| 179 | + (h1["rezone"] & h1["post_T0"]).astype(float), np.log(h1["superficie_habitable_m2"] / 135.0), | |
| 180 | + np.log(h1["superficie_terrain_m2"] / 600.0), h1["age_vente"].astype(float), t_annees]) | |
| 181 | + y = h1["lnP"].to_numpy() | |
| 182 | + beta, *_ = np.linalg.lstsq(X, y, rcond=None) | |
| 183 | + resid = y - X @ beta; nobs, k = X.shape | |
| 184 | + s2 = resid @ resid / (nobs - k); cov = s2 * np.linalg.inv(X.T @ X); se = np.sqrt(np.diag(cov)) | |
| 185 | + noms = ["const", "traite", "post", "traite_x_post", "ln_hab", "ln_terr", "age", "tendance"] | |
| 186 | + return {"moyennes_lnP": m, "effectifs": n, "delta_2x2": delta_2x2, "delta_2x2_pct": math.exp(delta_2x2) - 1.0, | |
| 187 | + "coef": dict(zip(noms, beta.tolist())), "se": dict(zip(noms, se.tolist())), | |
| 188 | + "delta_reg": float(beta[3]), "se_delta": float(se[3]), "delta_reg_pct": math.exp(float(beta[3])) - 1.0, | |
| 189 | + "n_obs": int(nobs), "sigma_resid": float(math.sqrt(s2))} | |
| 190 | + | |
| 191 | + | |
| 192 | +def prime_theorique_moyenne(ventes: pd.DataFrame) -> float: | |
| 193 | + """Moyenne de la prime injectée sur les ventes traitées (vérité terrain du générateur).""" | |
| 194 | + tr = ventes[ventes["traite_post"]] | |
| 195 | + return float(np.mean([math.exp(prime_rezonage(a)) - 1.0 for a in tr["age_vente"]])) | |
| 196 | + | |
| 197 | + | |
| 198 | +# ----------------------------------------------------------------------------- outils du chapitre (règles, options, droits acquis) | |
| 199 | + | |
| 200 | +def densite_admissible(s_terrain: float, s_min_par_logement: float, logements_max_grille: int, cos_max: float, s_min_lot: float) -> dict: | |
| 201 | + """Nombre de logements et aire de plancher admissibles d'après une grille des usages (lecture de la grille).""" | |
| 202 | + conforme_lot = s_terrain >= s_min_lot | |
| 203 | + n_densite = math.floor(s_terrain / s_min_par_logement) | |
| 204 | + n = max(0, min(n_densite, logements_max_grille)) | |
| 205 | + return {"lot_conforme": conforme_lot, "logements_par_densite": n_densite, "logements_admissibles": n, "aire_plancher_max_m2": cos_max * s_terrain} | |
| 206 | + | |
| 207 | + | |
| 208 | +def contribution_parcs(valeur_site: float, superficie_site: float, taux: float = 0.10) -> dict: | |
| 209 | + """Contribution pour fins de parcs (LAU 117.4 al. 1) : au plus 10 % de la superficie et de la valeur du site.""" | |
| 210 | + return {"terrain_max_m2": taux * superficie_site, "somme_max": taux * valeur_site, "taux": taux} | |
| 211 | + | |
| 212 | + | |
| 213 | +def valeur_residuelle_redeveloppement(n_logements: int, s_logement_m2: float, prix_neuf_m2: float, cout_constr_m2: float, | |
| 214 | + taux_indirects: float, demolition: float, marge_promoteur: float, financement: float, | |
| 215 | + taux_parcs: float = 0.10, charge_inclusion: float = 0.0) -> dict: | |
| 216 | + """Valeur résiduelle du terrain redéveloppé : V_L = [GDV − C_dir − C_ind − démolition − marge − financement − inclusion] / (1 + taux_parcs). | |
| 217 | + La contribution pour fins de parcs est proportionnelle à la valeur du site : V_L·taux_parcs, d'où la division.""" | |
| 218 | + gdv = n_logements * s_logement_m2 * prix_neuf_m2 | |
| 219 | + c_dir = n_logements * s_logement_m2 * cout_constr_m2 | |
| 220 | + c_ind = taux_indirects * c_dir | |
| 221 | + marge = marge_promoteur * gdv | |
| 222 | + fin = financement * gdv | |
| 223 | + reste = gdv - c_dir - c_ind - demolition - marge - fin - charge_inclusion | |
| 224 | + v_l = reste / (1.0 + taux_parcs) | |
| 225 | + return {"gdv": gdv, "couts_directs": c_dir, "couts_indirects": c_ind, "demolition": demolition, "marge": marge, "financement": fin, | |
| 226 | + "inclusion": charge_inclusion, "reste_avant_parcs": reste, "contribution_parcs": v_l * taux_parcs, "valeur_residuelle": v_l} | |
| 227 | + | |
| 228 | + | |
| 229 | +def prime_option(v_actuel: float, v_redev: float) -> float: | |
| 230 | + """Prime d'option de redéveloppement = max(0, V_redev − V_actuel).""" | |
| 231 | + return max(0.0, v_redev - v_actuel) | |
| 232 | + | |
| 233 | + | |
| 234 | +def valeur_attendue_rezonage(v_actuel: float, v_redev: float, p: float, delai_annees: float, r: float, cout_demande: float = 0.0) -> float: | |
| 235 | + """Valeur attendue d'un immeuble dont le rezonage (ou le PPCMOI) est probable : V = V_act + p·max(0, V_redev − V_act)·(1+r)^−T − c.""" | |
| 236 | + return v_actuel + p * prime_option(v_actuel, v_redev) * (1.0 + r) ** (-delai_annees) - cout_demande | |
| 237 | + | |
| 238 | + | |
| 239 | +def grille_attendue(v_actuel: float, v_redev: float, probs, delais, r: float) -> np.ndarray: | |
| 240 | + return np.array([[valeur_attendue_rezonage(v_actuel, v_redev, p, T, r) - v_actuel for T in delais] for p in probs]) | |
| 241 | + | |
| 242 | + | |
| 243 | +def valeur_surplus_derogatoire(delta_noi: float, R: float, lam: float) -> float: | |
| 244 | + """Surplus de revenu protégé par droits acquis, extinction au taux de risque λ : V_Δ = ΔRNE / (R + λ).""" | |
| 245 | + return delta_noi / (R + lam) | |
| 246 | + | |
| 247 | + | |
| 248 | +def valeur_surplus_horizon(delta_noi: float, R: float, lam: float, T: float) -> float: | |
| 249 | + """Même surplus mais sur un horizon fini T (annuités continues) : V_Δ(T) = ΔRNE·[1 − e^{−(R+λ)T}]/(R+λ).""" | |
| 250 | + return delta_noi * (1.0 - math.exp(-(R + lam) * T)) / (R + lam) | |
| 251 | + | |
| 252 | + | |
| 253 | +def valeur_attendue_zone_agricole(v_agricole: float, v_si_autorise: float, p: float, delai_annees: float, r: float, cout_demande: float) -> float: | |
| 254 | + """Zone agricole : V = p·V_autorisé·(1+r)^−T + (1−p)·V_agricole − coût de la demande.""" | |
| 255 | + return p * v_si_autorise * (1.0 + r) ** (-delai_annees) + (1.0 - p) * v_agricole - cout_demande | |
| 256 | + | |
| 257 | + | |
| 258 | +# ----------------------------------------------------------------------------- exemple intégral (paramètres déclarés) | |
| 259 | + | |
| 260 | +EXEMPLE = { | |
| 261 | + # lecture de la grille (zone H2 fictive de Val-Fictif) | |
| 262 | + "grille": {"s_min_lot": 450.0, "s_min_par_logement": 150.0, "logements_max": 4, "cos_max": 0.60, "marge_avant": 6.0, "marge_lat": 2.0, "marge_arr": 9.0, "hauteur_max_etages": 3}, | |
| 263 | + # lot témoin de l'exemple : maison 1958 de 95 m² sur 600 m², îlot rezoné | |
| 264 | + "lot": {"superficie_terrain_m2": 600.0, "superficie_habitable_m2": 95.0, "annee": 1958, "v_actuel": 380000.0}, | |
| 265 | + # redéveloppement en quadruplex : 4 × 90 m² (hypothèses, cf. encadré synthétique) | |
| 266 | + "redev": {"n_logements": 4, "s_logement_m2": 90.0, "prix_neuf_m2": 5800.0, "cout_constr_m2": 2900.0, "taux_indirects": 0.12, | |
| 267 | + "demolition": 30000.0, "marge_promoteur": 0.15, "financement": 0.03, "taux_parcs": 0.10}, | |
| 268 | + "option": {"r": 0.08, "probs": [0.25, 0.50, 0.75, 1.00], "delais": [0.5, 1.0, 2.0, 3.0], "cout_demande": 8000.0}, | |
| 269 | + # duplex dérogatoire protégé par droits acquis dans une zone H1 | |
| 270 | + "droits_acquis": {"loyer_mensuel_logement": 1450.0, "ratio_depenses_duplex": 0.35, "loyer_mensuel_maison": 2300.0, "ratio_depenses_maison": 0.30, | |
| 271 | + "R": 0.055, "lambdas": [0.0, 0.01, 0.02, 0.05], "v_conforme": 430000.0, "horizon": 25.0}, | |
| 272 | + "parcs": {"valeur_site": 960000.0, "superficie_site": 2400.0}, | |
| 273 | + "agricole": {"superficie_ha": 5.0, "v_ha_agricole": 15000.0, "v_lot_residentiel": 90000.0, "superficie_lot_ha": 0.5, "p": 0.30, "delai": 1.5, "r": 0.06, "cout_demande": 5000.0}, | |
| 274 | +} | |
| 275 | + | |
| 276 | + | |
| 277 | +def exemple_complet(ventes: pd.DataFrame | None = None) -> dict: | |
| 278 | + E = EXEMPLE | |
| 279 | + g = densite_admissible(E["lot"]["superficie_terrain_m2"], E["grille"]["s_min_par_logement"], E["grille"]["logements_max"], E["grille"]["cos_max"], E["grille"]["s_min_lot"]) | |
| 280 | + rd = valeur_residuelle_redeveloppement(**E["redev"]) | |
| 281 | + v_act = E["lot"]["v_actuel"] | |
| 282 | + prime = prime_option(v_act, rd["valeur_residuelle"]) | |
| 283 | + G = grille_attendue(v_act, rd["valeur_residuelle"], E["option"]["probs"], E["option"]["delais"], E["option"]["r"]) | |
| 284 | + da = E["droits_acquis"] | |
| 285 | + noi_dup = 2 * 12 * da["loyer_mensuel_logement"] * (1 - da["ratio_depenses_duplex"]) | |
| 286 | + noi_mai = 12 * da["loyer_mensuel_maison"] * (1 - da["ratio_depenses_maison"]) | |
| 287 | + d_noi = noi_dup - noi_mai | |
| 288 | + v_delta = {lam: valeur_surplus_derogatoire(d_noi, da["R"], lam) for lam in da["lambdas"]} | |
| 289 | + v_delta_T = {lam: valeur_surplus_horizon(d_noi, da["R"], lam, da["horizon"]) for lam in da["lambdas"]} | |
| 290 | + pc = contribution_parcs(E["parcs"]["valeur_site"], E["parcs"]["superficie_site"]) | |
| 291 | + ag = E["agricole"] | |
| 292 | + v_agr = ag["superficie_ha"] * ag["v_ha_agricole"] | |
| 293 | + v_aut = ag["v_lot_residentiel"] + (ag["superficie_ha"] - ag["superficie_lot_ha"]) * ag["v_ha_agricole"] | |
| 294 | + v_att_agr = valeur_attendue_zone_agricole(v_agr, v_aut, ag["p"], ag["delai"], ag["r"], ag["cout_demande"]) | |
| 295 | + out = {"grille": g, "redev": rd, "prime_option": prime, "prime_option_pct": prime / v_act, "grille_attendue": G, | |
| 296 | + "exemple_p75_T2": valeur_attendue_rezonage(v_act, rd["valeur_residuelle"], 0.75, 2.0, E["option"]["r"], E["option"]["cout_demande"]), | |
| 297 | + "noi_duplex": noi_dup, "noi_maison": noi_mai, "delta_noi": d_noi, "v_delta": v_delta, "v_delta_T": v_delta_T, | |
| 298 | + "v_droits_acquis_lam02": da["v_conforme"] + v_delta[0.02], "parcs": pc, "v_agricole": v_agr, "v_si_autorise": v_aut, "v_attendue_agricole": v_att_agr} | |
| 299 | + if ventes is not None: | |
| 300 | + paires = apparier(ventes); out["paires"] = paires; out["resume_paires"] = resume_paires(paires); out["did"] = did(ventes) | |
| 301 | + out["prime_theorique_moyenne"] = prime_theorique_moyenne(ventes) | |
| 302 | + return out | |
| 303 | + | |
| 304 | + | |
| 305 | +def charger_ou_generer(ecrire: bool = True) -> tuple[pd.DataFrame, pd.DataFrame]: | |
| 306 | + lots = generer_lots(); ventes = generer_ventes(lots) | |
| 307 | + if ecrire: | |
| 308 | + d = ROOT / "data/synthetic"; d.mkdir(parents=True, exist_ok=True) | |
| 309 | + lots.to_csv(d / "ch08_zonage_lots.csv", index=False); ventes.to_csv(d / "ch08_zonage_ventes.csv", index=False) | |
| 310 | + return lots, ventes | |
| 311 | + | |
| 312 | + | |
| 313 | +def main(ecrire: bool = True) -> dict: | |
| 314 | + lots, ventes = charger_ou_generer(ecrire) | |
| 315 | + res = exemple_complet(ventes) | |
| 316 | + f = lambda x: f"{x:,.0f}".replace(",", " ") | |
| 317 | + print("=== Ch. 8 — DONNÉES SYNTHÉTIQUES (graine 20261031) ===") | |
| 318 | + print(f"Lots : {len(lots)} ; par zone : {lots['zone_initiale'].value_counts().to_dict()} ; rezonés H1→H2 : {int(lots['rezone'].sum())}") | |
| 319 | + print(f"Ventes : {len(ventes)} ; H1 : {int((ventes['zone_initiale']=='H1').sum())} ; traitées (îlot rezoné, après T0) : {int(ventes['traite_post'].sum())}") | |
| 320 | + rp = res["resume_paires"] | |
| 321 | + print(f"\n--- Paires (traité vs témoin ajusté) : n = {rp['n']} ---") | |
| 322 | + print(f"Différence moyenne {f(rp['moyenne'])} $ (médiane {f(rp['mediane'])} $, écart-type {f(rp['ecart_type'])} $, erreur-type {f(rp['erreur_type_moyenne'])} $)") | |
| 323 | + print(f"En % du témoin ajusté : moyenne {rp['moyenne_pct']*100:.2f} %, médiane {rp['mediane_pct']*100:.2f} %, IC95 [{rp['ic95_pct'][0]*100:.2f} ; {rp['ic95_pct'][1]*100:.2f}]") | |
| 324 | + print(f"Maisons < 40 ans : n = {rp['n_jeunes']}, +{rp['moyenne_pct_jeunes']*100:.2f} % ; ≥ 40 ans : n = {rp['n_vieux']}, +{rp['moyenne_pct_vieux']*100:.2f} % ; pente {rp['pente_age']*100:.3f} pt/an") | |
| 325 | + print(f"Prime théorique moyenne injectée : {res['prime_theorique_moyenne']*100:.2f} %") | |
| 326 | + d = res["did"] | |
| 327 | + print(f"\n--- Doubles différences (H1, n = {d['n_obs']}) ---") | |
| 328 | + for k, v in d["moyennes_lnP"].items(): | |
| 329 | + print(f" ln P moyen traité={k[0]} post={k[1]} : {v:.4f} (n = {d['effectifs'][k]})") | |
| 330 | + print(f"δ (2×2) = {d['delta_2x2']:.4f} → {d['delta_2x2_pct']*100:.2f} % ; δ (régression) = {d['delta_reg']:.4f} (e.-t. {d['se_delta']:.4f}) → {d['delta_reg_pct']*100:.2f} % ; σ résiduel {d['sigma_resid']:.4f}") | |
| 331 | + print(f" coefficients : " + ", ".join(f"{k} {v:.4f}" for k, v in d["coef"].items())) | |
| 332 | + g = res["grille"] | |
| 333 | + print(f"\n--- Lecture de la grille H2 (lot 600 m²) : lot conforme {g['lot_conforme']}, logements par densité {g['logements_par_densite']}, admissibles {g['logements_admissibles']}, plancher max {g['aire_plancher_max_m2']:.0f} m²") | |
| 334 | + rd = res["redev"] | |
| 335 | + print(f"--- Redéveloppement en quadruplex : GDV {f(rd['gdv'])} ; directs {f(rd['couts_directs'])} ; indirects {f(rd['couts_indirects'])} ; démolition {f(rd['demolition'])} ; marge {f(rd['marge'])} ; financement {f(rd['financement'])}") | |
| 336 | + print(f" reste avant parcs {f(rd['reste_avant_parcs'])} ; contribution parcs {f(rd['contribution_parcs'])} ; valeur résiduelle {f(rd['valeur_residuelle'])} ; V_actuel {f(EXEMPLE['lot']['v_actuel'])} ; prime d'option {f(res['prime_option'])} ({res['prime_option_pct']*100:.1f} %)") | |
| 337 | + print(" grille attendue (prime espérée, $) lignes p = " + str(EXEMPLE["option"]["probs"]) + " ; colonnes T = " + str(EXEMPLE["option"]["delais"])) | |
| 338 | + for p, row in zip(EXEMPLE["option"]["probs"], res["grille_attendue"]): | |
| 339 | + print(" p = %.2f : " % p + " ".join(f(x) for x in row)) | |
| 340 | + print(f" exemple p = 0,75, T = 2, coût 8 000 $ : V = {f(res['exemple_p75_T2'])}") | |
| 341 | + print(f"--- Droits acquis : RNE duplex {f(res['noi_duplex'])} ; RNE maison {f(res['noi_maison'])} ; ΔRNE {f(res['delta_noi'])}") | |
| 342 | + for lam in EXEMPLE["droits_acquis"]["lambdas"]: | |
| 343 | + print(f" λ = {lam:.2f} : V_Δ = {f(res['v_delta'][lam])} (horizon 25 ans : {f(res['v_delta_T'][lam])})") | |
| 344 | + print(f" V avec droits acquis (λ = 2 %) = {f(res['v_droits_acquis_lam02'])}") | |
| 345 | + print(f"--- Parcs : terrain max {res['parcs']['terrain_max_m2']:.0f} m² ou somme max {f(res['parcs']['somme_max'])}") | |
| 346 | + print(f"--- Zone agricole : V agricole {f(res['v_agricole'])} ; V si autorisé {f(res['v_si_autorise'])} ; V attendue {f(res['v_attendue_agricole'])}") | |
| 347 | + return {"lots": lots, "ventes": ventes, **res} | |
| 348 | + | |
| 349 | + | |
| 350 | +if __name__ == "__main__": | |
| 351 | + main() | |
added
analysis/python/cases/ch10_controle_rapport.py
+215 −0
@@ -0,0 +1,215 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Chapitre 10 — contrôle automatique de la présence des douze éléments du contenu minimal d'un rapport écrit | |
| 3 | +(NPP de l'OEAQ, norme 2, règle 2.3) dans un texte, et calculs auxiliaires du chapitre (probabilité de détection | |
| 4 | +d'une lacune par révisions indépendantes, incidence d'une hypothèse extraordinaire, échéances réglementaires). | |
| 5 | + | |
| 6 | +Les quatre extraits de rapports analysés ici sont FICTIFS, rédigés pour le traité (aucun immeuble, aucun client, | |
| 7 | +aucun évaluateur réel). Le contrôle est lexical : il détecte la présence de formulations caractéristiques, pas la | |
| 8 | +qualité du contenu — limite discutée au chapitre. | |
| 9 | + | |
| 10 | +Usage (racine du dépôt) : PYTHONPATH=analysis/python python3 analysis/python/cases/ch10_controle_rapport.py""" | |
| 11 | +from __future__ import annotations | |
| 12 | +import re, sys, pathlib, unicodedata, datetime as dt | |
| 13 | +from dataclasses import dataclass | |
| 14 | + | |
| 15 | +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1])) | |
| 16 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 17 | + | |
| 18 | +# ---------------------------------------------------------------------------------------------------------------- | |
| 19 | +# 1. Les douze éléments de la règle 2.3 (paraphrase ; source : NPP OEAQ, norme 2, règle 2.3, éléments 1 à 12) | |
| 20 | +# ---------------------------------------------------------------------------------------------------------------- | |
| 21 | +@dataclass(frozen=True) | |
| 22 | +class Element: | |
| 23 | + no: int | |
| 24 | + cle: str | |
| 25 | + libelle: str | |
| 26 | + strict: tuple # motifs caractéristiques (lexique strict) | |
| 27 | + souple: tuple # motifs supplémentaires, plus larges (lexique souple = strict + souple) | |
| 28 | + | |
| 29 | +def _n(s: str) -> str: | |
| 30 | + """Normalise : minuscules, sans accents, espaces simples, apostrophes droites.""" | |
| 31 | + s = unicodedata.normalize("NFKD", s).encode("ascii", "ignore").decode("ascii") | |
| 32 | + s = s.replace("’", "'").lower() | |
| 33 | + return re.sub(r"\s+", " ", s) | |
| 34 | + | |
| 35 | +ELEMENTS: list[Element] = [ | |
| 36 | + Element(1, "identification", "Identification physique et légale de l'immeuble", | |
| 37 | + (r"\blot\s+\d", r"cadastre du quebec", r"designation cadastrale", r"identification (physique|legale)"), | |
| 38 | + (r"\badresse\b", r"\bsitue au\b", r"matricule")), | |
| 39 | + Element(2, "droits", "Droits de propriété évalués", | |
| 40 | + (r"pleine propriete", r"droits? (de propriete )?evalues?", r"droit (reel )?evalue", r"fief simple"), | |
| 41 | + (r"\busufruit\b", r"emphyteo", r"indivis", r"copropriete divise")), | |
| 42 | + Element(3, "but_fin", "But (valeur recherchée) et fin (usage prévu) de l'évaluation", | |
| 43 | + (r"\bbut de l'evaluation", r"\bfin de l'evaluation", r"aux fins (de|d')", r"usage prevu du rapport"), | |
| 44 | + (r"destinataire", r"financement hypothecaire", r"\bmandat\b")), | |
| 45 | + Element(4, "definition_valeur", "Définition de la valeur recherchée", | |
| 46 | + (r"valeur marchande", r"definition de la valeur", r"valeur recherchee", r"prix (sincere )?le plus probable"), | |
| 47 | + (r"\bvaleur\b.{0,40}\bdefini", r"valeur d'investissement", r"valeur reelle")), | |
| 48 | + Element(5, "date", "Date d'évaluation (date effective de l'opinion)", | |
| 49 | + (r"date d'evaluation", r"date effective", r"en date du \d", r"\bau \d{1,2}(er)? [a-z]+ \d{4}\b"), | |
| 50 | + (r"\b(janvier|fevrier|mars|avril|mai|juin|juillet|aout|septembre|octobre|novembre|decembre) \d{4}\b",)), | |
| 51 | + Element(6, "etendue", "Étendue du travail (recherches, vérifications, inspection)", | |
| 52 | + (r"etendue (du travail|des recherches|de la mission)", r"portee du mandat", r"travaux (effectues|realises)", | |
| 53 | + r"inspect(e|ee|ion)"), | |
| 54 | + (r"\bvisite\b", r"recherches? effectuee", r"verifi(e|cation)")), | |
| 55 | + Element(7, "reserves_hypotheses", "Réserves limitant la portée de l'acte et hypothèses de base (dont extraordinaires)", | |
| 56 | + (r"hypothese", r"\breserves?\b", r"condition(s)? limitative"), | |
| 57 | + (r"\bpresum", r"\bsuppos", r"sous reserve")), | |
| 58 | + Element(8, "raisonnement", "Traitement de l'information et raisonnement conduisant à la conclusion", | |
| 59 | + (r"methode (de comparaison|du cout|du revenu|de la parite|des revenus)", r"\bajustements?\b", r"reconciliation", | |
| 60 | + r"raisonnement", r"analyse (des|de) (ventes|donnees|marche)"), | |
| 61 | + (r"\bcomparables?\b", r"taux (global|de capitalisation)", r"\bconclu(sion|ons)\b")), | |
| 62 | + Element(9, "umpp", "Usage le meilleur et le plus profitable (PHMU)", | |
| 63 | + (r"usage le meilleur et le plus profitable", r"plus haute et meilleure utilisation", r"\bumpp\b", r"\bphmu\b"), | |
| 64 | + (r"usage optimal", r"meilleur usage")), | |
| 65 | + Element(10, "rejet_methode", "Explication du rejet d'une méthode traditionnelle", | |
| 66 | + (r"methode[^.]{0,80}(ecartee|rejetee|non retenue|non appliquee|n'a pas ete (retenue|appliquee))", | |
| 67 | + r"rejet de la methode", r"(ecartee|rejetee|non retenue)[^.]{0,40}methode"), | |
| 68 | + (r"n'a pas ete (utilisee|employee)", r"\bnon pertinente\b")), | |
| 69 | + Element(11, "conformite", "Conformité à la substance de la norme 1 et écarts aux directives", | |
| 70 | + (r"conform(e|ement|ite) aux normes de pratique", r"normes de pratique professionnelle", | |
| 71 | + r"ecart[^.]{0,60}directive", r"directive[^.]{0,60}ecart"), | |
| 72 | + (r"\bnormes?\b", r"\boeaq\b", r"ordre des evaluateurs")), | |
| 73 | + Element(12, "attestation", "Attestation signée et datée", | |
| 74 | + (r"j'atteste", r"\batteste(nt)? par la presente", r"\battestation\b"), | |
| 75 | + (r"au mieux de (ma|notre) connaissance", r"\bsign(e|ee|ature)\b")), | |
| 76 | +] | |
| 77 | + | |
| 78 | +def controler(texte: str, lexique: str = "strict") -> dict: | |
| 79 | + """Retourne, pour chaque élément, (détecté: bool, motif ayant déclenché la détection ou None).""" | |
| 80 | + t = _n(texte) | |
| 81 | + res = {} | |
| 82 | + for e in ELEMENTS: | |
| 83 | + motifs = e.strict + (e.souple if lexique == "souple" else ()) | |
| 84 | + hit = None | |
| 85 | + for m in motifs: | |
| 86 | + if re.search(m, t): | |
| 87 | + hit = m; break | |
| 88 | + res[e.cle] = (hit is not None, hit) | |
| 89 | + return res | |
| 90 | + | |
| 91 | +def taux_conformite(res: dict) -> float: | |
| 92 | + """Équation (10.2) : proportion des douze éléments détectés.""" | |
| 93 | + return sum(1 for d, _ in res.values() if d) / len(ELEMENTS) | |
| 94 | + | |
| 95 | +def manquants(res: dict) -> list[int]: | |
| 96 | + return [e.no for e in ELEMENTS if not res[e.cle][0]] | |
| 97 | + | |
| 98 | +def p_detection(d: float, n: int) -> float: | |
| 99 | + """Probabilité qu'au moins une de n révisions indépendantes, chacune de puissance d, relève une lacune.""" | |
| 100 | + return 1.0 - (1.0 - d) ** n | |
| 101 | + | |
| 102 | +def incidence_hypothese(v_sous_h: float, v_sans_h: float) -> tuple[float, float]: | |
| 103 | + """Incidence d'une hypothèse extraordinaire : écart absolu et relatif (base : valeur sans l'hypothèse).""" | |
| 104 | + return v_sous_h - v_sans_h, (v_sous_h - v_sans_h) / v_sans_h | |
| 105 | + | |
| 106 | +def esperance_sous_incertitude(p: float, v_sous_h: float, v_sans_h: float) -> float: | |
| 107 | + return p * v_sous_h + (1 - p) * v_sans_h | |
| 108 | + | |
| 109 | +def fin_conservation(dernier_service: dt.date, annees: int = 5) -> dt.date: | |
| 110 | + """Règlement sur la tenue des dossiers (C-26, r. 133, art. 6) : au moins 5 ans après le dernier service.""" | |
| 111 | + return dernier_service.replace(year=dernier_service.year + annees) | |
| 112 | + | |
| 113 | +def bilan_formation(heures_total: float, heures_ethique: float, report_anterieur: float = 0.0) -> dict: | |
| 114 | + """Règlement sur la formation continue (C-26, r. 127.1) : 30 h par période de 2 ans dont 3 h éthique/déontologie/normes ; | |
| 115 | + report maximal de 5 h vers la période suivante.""" | |
| 116 | + requis_total, requis_ethique, report_max = 30.0, 3.0, 5.0 | |
| 117 | + dispo = heures_total + min(report_anterieur, report_max) | |
| 118 | + deficit_total = max(0.0, requis_total - dispo) | |
| 119 | + deficit_ethique = max(0.0, requis_ethique - heures_ethique) | |
| 120 | + report_suivant = min(report_max, max(0.0, dispo - requis_total)) | |
| 121 | + return {"deficit_total": deficit_total, "deficit_ethique": deficit_ethique, "report_suivant": report_suivant, | |
| 122 | + "conforme": deficit_total == 0 and deficit_ethique == 0} | |
| 123 | + | |
| 124 | +# ---------------------------------------------------------------------------------------------------------------- | |
| 125 | +# 2. Extraits FICTIFS de rapports (rédigés pour le traité ; immeubles, parties et signataires inexistants) | |
| 126 | +# ---------------------------------------------------------------------------------------------------------------- | |
| 127 | +EXTRAITS = { | |
| 128 | +"A": """EXTRAIT FICTIF A — « Rapport abrégé » | |
| 129 | +Objet : résidence unifamiliale à étages située au 123, rue des Érables (adresse fictive), secteur Hull, Gatineau. | |
| 130 | +Mandat : estimer la valeur de la propriété pour le compte de l'institution prêteuse, aux fins de financement hypothécaire. | |
| 131 | +Description : cottage de deux étages construit en 1998, superficie habitable de 165 m², terrain de 620 m², garage simple, | |
| 132 | +sous-sol aménagé. Le bâtiment est en bon état général selon la visite extérieure effectuée. | |
| 133 | +Analyse : trois ventes comparables du secteur ont été retenues et ajustées pour la superficie, l'âge et le garage. | |
| 134 | +Les prix ajustés s'établissent entre 538 000 $ et 571 000 $. Par la méthode de comparaison, nous estimons la valeur | |
| 135 | +à 555 000 $. Ce montant est valable pour une période de six mois. | |
| 136 | +Signature : Évaluateur fictif, É.A.""", | |
| 137 | + | |
| 138 | +"B": """EXTRAIT FICTIF B — « Rapport narratif, section Réserves et conclusion » | |
| 139 | +Identification : lot 0 000 000 du cadastre du Québec (numéro fictif), circonscription foncière de Hull, bâtiment résidentiel. | |
| 140 | +Droits évalués : pleine propriété, libre de toute charge autre que celles mentionnées au certificat de localisation. | |
| 141 | +But et fin : estimer la valeur marchande, telle que définie aux Normes de pratique professionnelle de l'OEAQ, en date | |
| 142 | +du 1er septembre 2026, aux fins d'une décision d'achat par le client. | |
| 143 | +Usage le meilleur et le plus profitable : l'usage résidentiel unifamilial actuel est conforme au zonage et demeure | |
| 144 | +l'usage optimal du site tel qu'aménagé. | |
| 145 | +Hypothèses : nous présumons que le permis de construction pour l'agrandissement arrière a été obtenu et que les travaux | |
| 146 | +sont conformes au Code. La méthode du coût a également été appliquée. | |
| 147 | +Conclusion : la valeur marchande est de 612 000 $. Le présent rapport est conforme aux normes de pratique professionnelle. | |
| 148 | +J'atteste par la présente, au mieux de ma connaissance, que les faits énoncés sont vrais et exacts.""", | |
| 149 | + | |
| 150 | +"C": """EXTRAIT FICTIF C — « Rapport de mise à jour » | |
| 151 | +Le présent rapport de mise à jour se rapporte au rapport narratif complet portant sur l'immeuble de douze logements | |
| 152 | +situé au 456, avenue Fictive, Montréal (lot 0 000 001 du cadastre du Québec, numéro fictif), préparé par notre firme | |
| 153 | +pour le même client en date du 1er mars 2026. Les réserves et hypothèses de base du rapport original demeurent applicables. | |
| 154 | +L'inspection a été mise à jour le 10 octobre 2026 ; aucun changement physique n'a été constaté. | |
| 155 | +Les loyers en vigueur ont augmenté de 3,1 % et le taux global de capitalisation retenu passe de 5,25 % à 5,40 %. | |
| 156 | +Par la méthode du revenu, l'indication de valeur en date du 15 octobre 2026 est de 2 410 000 $, comparativement à | |
| 157 | +2 450 000 $ au rapport original. La méthode du coût, écartée au rapport original pour un immeuble de 1965, demeure | |
| 158 | +non retenue. Évaluateur fictif, É.A.""", | |
| 159 | + | |
| 160 | +"D": """EXTRAIT FICTIF D — « Sommaire d'un rapport narratif complet » | |
| 161 | +Identification physique et légale : lot 0 000 002 du cadastre du Québec (numéro fictif), 789, rue Imaginaire, Québec. | |
| 162 | +Droits évalués : pleine propriété. But et fin de l'évaluation : estimer la valeur marchande aux fins d'un règlement | |
| 163 | +successoral ; le rapport est destiné au liquidateur. Définition de la valeur : la valeur marchande est définie comme | |
| 164 | +le prix sincère le plus probable, conformément aux Normes de pratique professionnelle de l'OEAQ. Date d'évaluation : | |
| 165 | +1er septembre 2026. Étendue du travail : inspection complète de l'immeuble le 28 août 2026, recherche de titres, | |
| 166 | +analyse de quatorze ventes de triplex. Réserves et hypothèses de base : voir l'annexe ; aucune hypothèse extraordinaire. | |
| 167 | +Usage le meilleur et le plus profitable : usage résidentiel locatif actuel. Raisonnement : méthode de comparaison | |
| 168 | +(cinq comparables, ajustements documentés) et méthode du revenu, réconciliation pondérée. La méthode du coût a été | |
| 169 | +écartée en raison de l'âge du bâtiment et de la difficulté de mesurer la dépréciation. Le rapport est conforme à la | |
| 170 | +substance de la norme 1 ; aucun écart aux directives. J'atteste par la présente, au mieux de ma connaissance et de ma | |
| 171 | +conviction, que les faits énoncés sont vrais et exacts. Signé et daté.""", | |
| 172 | +} | |
| 173 | + | |
| 174 | +def main(verbose: bool = True) -> dict: | |
| 175 | + out = {"controle": {}, "taux": {}, "manquants": {}} | |
| 176 | + for nom, txt in EXTRAITS.items(): | |
| 177 | + for lex in ("strict", "souple"): | |
| 178 | + r = controler(txt, lex) | |
| 179 | + out["controle"][(nom, lex)] = r | |
| 180 | + out["taux"][(nom, lex)] = taux_conformite(r) | |
| 181 | + out["manquants"][(nom, lex)] = manquants(r) | |
| 182 | + # probabilités de détection (modèle de révisions indépendantes) | |
| 183 | + grille_d = (0.5, 0.7, 0.9); grille_n = (1, 2, 3) | |
| 184 | + out["p_detection"] = {(d, n): p_detection(d, n) for d in grille_d for n in grille_n} | |
| 185 | + # incidence d'une hypothèse extraordinaire (chiffres FICTIFS de l'extrait B et de l'exercice 10.5) | |
| 186 | + out["incidence_B"] = incidence_hypothese(612000, 575000) | |
| 187 | + out["esperance_B"] = esperance_sous_incertitude(0.6, 612000, 575000) | |
| 188 | + # échéances réglementaires (exercice 10.2) | |
| 189 | + out["conservation"] = fin_conservation(dt.date(2026, 11, 15)) | |
| 190 | + out["formation"] = bilan_formation(heures_total=26, heures_ethique=2, report_anterieur=5) | |
| 191 | + out["formation_b"] = bilan_formation(heures_total=33, heures_ethique=3, report_anterieur=0) | |
| 192 | + if verbose: | |
| 193 | + print("== Contrôle lexical des douze éléments (règle 2.3) — extraits FICTIFS") | |
| 194 | + for nom in EXTRAITS: | |
| 195 | + for lex in ("strict", "souple"): | |
| 196 | + r = out["controle"][(nom, lex)] | |
| 197 | + print(f" Extrait {nom} [{lex:6s}] : {sum(d for d,_ in r.values()):2d}/12 détectés " | |
| 198 | + f"(taux {out['taux'][(nom, lex)]:.3f}) ; manquants : {out['manquants'][(nom, lex)]}") | |
| 199 | + print("== Détail (lexique strict) :") | |
| 200 | + for nom in EXTRAITS: | |
| 201 | + r = out["controle"][(nom, "strict")] | |
| 202 | + print(" ", nom, " ".join(f"{e.no}:{'X' if r[e.cle][0] else '.'}" for e in ELEMENTS)) | |
| 203 | + print("== Probabilité de détection d'une lacune, 1-(1-d)^n :") | |
| 204 | + for d in grille_d: | |
| 205 | + print(" d =", d, " ".join(f"n={n}: {out['p_detection'][(d,n)]:.3f}" for n in grille_n)) | |
| 206 | + dv, dr = out["incidence_B"] | |
| 207 | + print(f"== Incidence de l'hypothèse extraordinaire (extrait B, fictif) : {dv:,.0f} $ ({dr*100:.2f} %) ; " | |
| 208 | + f"espérance à p = 0,6 : {out['esperance_B']:,.0f} $") | |
| 209 | + print("== Fin de conservation minimale d'un dossier clos le 2026-11-15 :", out["conservation"]) | |
| 210 | + print("== Bilan de formation continue (26 h dont 2 h éthique, report 5 h) :", out["formation"]) | |
| 211 | + print("== Bilan de formation continue (33 h dont 3 h éthique) :", out["formation_b"]) | |
| 212 | + return out | |
| 213 | + | |
| 214 | +if __name__ == "__main__": | |
| 215 | + main() | |
added
analysis/python/cases/ch10_matrice_normes.py
+90 −0
@@ -0,0 +1,90 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Chapitre 10 — statistiques descriptives de la matrice des dispositions normatives du domaine C | |
| 3 | +(sources/normes-C.csv : 100 dispositions vérifiées le 2026-10-31) et chronologie documentaire des cadres normatifs | |
| 4 | +(dates tirées de sources/registry-C.csv). Imprime les chiffres cités au chapitre 10. | |
| 5 | + | |
| 6 | +Usage (racine du dépôt) : PYTHONPATH=analysis/python python3 analysis/python/cases/ch10_matrice_normes.py""" | |
| 7 | +from __future__ import annotations | |
| 8 | +import sys, pathlib, datetime as dt | |
| 9 | +import pandas as pd | |
| 10 | + | |
| 11 | +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1])) | |
| 12 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 13 | +MATRICE = ROOT / "sources/normes-C.csv" | |
| 14 | + | |
| 15 | +# Regroupement des 17 domaines de la matrice en six familles (choix de présentation de l'auteur) | |
| 16 | +FAMILLES = { | |
| 17 | + "OEAQ-NPP": "OEAQ — normes de pratique", "OEAQ-Déontologie": "OEAQ — déontologie", | |
| 18 | + "OEAQ-Règlements": "OEAQ — règlements", "Code des professions": "OEAQ — règlements", | |
| 19 | + "IVS": "Cadres hors Québec", "CUSPAP": "Cadres hors Québec", "USPAP": "Cadres hors Québec", | |
| 20 | + "RICS": "Cadres hors Québec", "IAAO": "Cadres hors Québec", | |
| 21 | + "Banque du Canada": "Finance et prudentiel", "BSIF": "Finance et prudentiel", "Finances Canada": "Finance et prudentiel", | |
| 22 | + "SCHL": "Finance et prudentiel", | |
| 23 | + "Statistique Canada": "Données de marché", "APCIQ": "Données de marché", "JLR": "Données de marché", | |
| 24 | + "Teranet–BNC": "Données de marché", | |
| 25 | +} | |
| 26 | + | |
| 27 | +# Chronologie documentaire (source_id du registre C ; dates vérifiées le 2026-10-31). | |
| 28 | +# publication : date de publication connue (None si non documentée) ; effet : date d'entrée en vigueur ; | |
| 29 | +# fin : fin de validité connue (None = en vigueur au 2026-10-31) ; statut : en_vigueur | remplace | projet | reference | |
| 30 | +CADRES = [ | |
| 31 | + dict(cadre="IAAO — Standard on AVMs (rév. 2018)", source="SRC-C-026", publication=None, effet=dt.date(2018, 7, 1), fin=None, statut="reference", precision="mois"), | |
| 32 | + dict(cadre="IAAO — Standard on Ratio Studies (2013)", source="SRC-C-024", publication=None, effet=dt.date(2013, 4, 1), fin=None, statut="reference", precision="mois"), | |
| 33 | + dict(cadre="IAAO — Standard on Mass Appraisal (rév. 2025)", source="SRC-C-025", publication=None, effet=dt.date(2025, 1, 1), fin=None, statut="reference", precision="annee"), | |
| 34 | + dict(cadre="USPAP 2024 (États-Unis)", source="SRC-C-021", publication=None, effet=dt.date(2024, 1, 1), fin=None, statut="en_vigueur", precision="jour", verification="partielle"), | |
| 35 | + dict(cadre="CUSPAP 2024 (Canada)", source="SRC-C-020", publication=None, effet=dt.date(2024, 1, 1), fin=dt.date(2026, 3, 31), statut="remplace", precision="jour"), | |
| 36 | + dict(cadre="NPP de l'OEAQ (Québec)", source="SRC-C-001", publication=None, effet=dt.date(2024, 2, 1), fin=None, statut="en_vigueur", precision="jour"), | |
| 37 | + dict(cadre="IVS 2025 (IVSC)", source="SRC-C-014", publication=dt.date(2024, 1, 31), effet=dt.date(2025, 1, 31), fin=None, statut="en_vigueur", precision="jour"), | |
| 38 | + dict(cadre="RICS Red Book Global (2025)", source="SRC-C-023", publication=dt.date(2024, 12, 11), effet=dt.date(2025, 1, 31), fin=None, statut="en_vigueur", precision="jour"), | |
| 39 | + dict(cadre="CUSPAP 2026 (Canada)", source="SRC-C-018", publication=None, effet=dt.date(2026, 4, 1), fin=None, statut="en_vigueur", precision="jour"), | |
| 40 | + dict(cadre="IVS 2028 — projet (exposure draft)", source="SRC-C-015", publication=dt.date(2026, 1, 1), effet=dt.date(2028, 1, 31), fin=None, statut="projet", precision="mois"), | |
| 41 | +] | |
| 42 | +DATE_REF = dt.date(2026, 10, 31) | |
| 43 | + | |
| 44 | +def charger() -> pd.DataFrame: | |
| 45 | + m = pd.read_csv(MATRICE) | |
| 46 | + m["famille"] = m["domaine"].map(FAMILLES) | |
| 47 | + assert m["famille"].notna().all(), "domaine sans famille" | |
| 48 | + m["n_chapitres"] = m["chapitres"].str.split(";").apply(len) | |
| 49 | + m["ch10"] = m["chapitres"].str.contains("ch10") | |
| 50 | + return m | |
| 51 | + | |
| 52 | +def statistiques(m: pd.DataFrame) -> dict: | |
| 53 | + par_domaine = m["domaine"].value_counts() | |
| 54 | + par_famille = m["famille"].value_counts() | |
| 55 | + par_chapitre = m["chapitres"].str.split(";").explode().value_counts() | |
| 56 | + oeaq = m["domaine"].str.startswith("OEAQ") | (m["domaine"] == "Code des professions") | |
| 57 | + return { | |
| 58 | + "n": len(m), "n_domaines": m["domaine"].nunique(), "n_chapitres_couverts": par_chapitre.size, | |
| 59 | + "par_domaine": par_domaine, "par_famille": par_famille, "par_chapitre": par_chapitre, | |
| 60 | + "n_oeaq": int(oeaq.sum()), "part_oeaq": float(oeaq.mean()), | |
| 61 | + "n_ch10": int(m["ch10"].sum()), "part_ch10": float(m["ch10"].mean()), | |
| 62 | + "ch10_par_domaine": m.loc[m["ch10"], "domaine"].value_counts(), | |
| 63 | + "moy_chapitres_par_disposition": float(m["n_chapitres"].mean()), | |
| 64 | + "max_chapitres_par_disposition": int(m["n_chapitres"].max()), | |
| 65 | + "part_multi_chapitres": float((m["n_chapitres"] > 1).mean()), | |
| 66 | + } | |
| 67 | + | |
| 68 | +def chronologie() -> pd.DataFrame: | |
| 69 | + c = pd.DataFrame(CADRES) | |
| 70 | + c["en_vigueur_2026_10_31"] = c.apply(lambda r: r["effet"] <= DATE_REF and (r["fin"] is None or r["fin"] >= DATE_REF) and r["statut"] != "projet", axis=1) | |
| 71 | + c["delai_publication_effet_jours"] = c.apply(lambda r: (r["effet"] - r["publication"]).days if r["publication"] else None, axis=1) | |
| 72 | + return c | |
| 73 | + | |
| 74 | +def main(verbose: bool = True) -> dict: | |
| 75 | + m = charger(); st = statistiques(m); ch = chronologie() | |
| 76 | + if verbose: | |
| 77 | + print(f"== Matrice C : {st['n']} dispositions, {st['n_domaines']} domaines, {st['n_chapitres_couverts']} chapitres couverts") | |
| 78 | + print("== Par famille :\n", st["par_famille"].to_string()) | |
| 79 | + print("== Par domaine :\n", st["par_domaine"].to_string()) | |
| 80 | + print(f"== Dispositions OEAQ + Code des professions : {st['n_oeaq']} ({st['part_oeaq']*100:.0f} %)") | |
| 81 | + print(f"== Dispositions renvoyant au ch. 10 : {st['n_ch10']} ({st['part_ch10']*100:.0f} %) ; par domaine :\n", st["ch10_par_domaine"].to_string()) | |
| 82 | + print("== Dix chapitres les plus cités :\n", st["par_chapitre"].head(10).to_string()) | |
| 83 | + print(f"== Chapitres par disposition : moyenne {st['moy_chapitres_par_disposition']:.2f}, max {st['max_chapitres_par_disposition']}, " | |
| 84 | + f"part multi-chapitres {st['part_multi_chapitres']*100:.0f} %") | |
| 85 | + pd.set_option("display.width", 200) | |
| 86 | + print("== Chronologie des cadres (registre C) :\n", ch[["cadre", "publication", "effet", "fin", "statut", "en_vigueur_2026_10_31", "delai_publication_effet_jours"]].to_string(index=False)) | |
| 87 | + return {"matrice": m, "stats": st, "chronologie": ch} | |
| 88 | + | |
| 89 | +if __name__ == "__main__": | |
| 90 | + main() | |
added
analysis/python/figures/ch01_figures.py
+127 −0
@@ -0,0 +1,127 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Figures du chapitre 1 (DONNÉES SYNTHÉTIQUES du cas 1, graine 20261031 ; simulations étiquetées). | |
| 3 | +Sorties (assets/figures/ch01/) : | |
| 4 | + ch01-fig-prix-valeur.pdf nuage prix observé vs valeur estimée (modèle hédonique de référence), comparables du ch. 16 en évidence | |
| 5 | + ch01-fig-residus.pdf distribution des écarts prix/valeur (résidus) et loi normale ajustée | |
| 6 | + ch01-fig-recherche.pdf simulation du modèle de recherche : offres, prix acceptés, délais de vente | |
| 7 | + ch01-fig-rosen.pdf schéma de l'équilibre hédonique de Rosen : enchères, offres et enveloppe des prix | |
| 8 | + ch01-fig-sensibilite-sigma.pdf sensibilité : bornes de l'intervalle à 95 % et probabilité d'un écart > 10 % selon sigma | |
| 9 | +Exécuter depuis la racine : python3 analysis/python/figures/ch01_figures.py""" | |
| 10 | +import sys, pathlib, math | |
| 11 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 12 | +sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) | |
| 13 | +import numpy as np | |
| 14 | +import matplotlib.pyplot as plt | |
| 15 | +from scipy.stats import norm | |
| 16 | +from traite.style import setup, palette, savefig, LARGEUR, fmt_dollars, fmt_pct | |
| 17 | +import ch01_valeur_prix as c1 | |
| 18 | + | |
| 19 | +def fig_prix_valeur(t): | |
| 20 | + m = t["modele"]; v = t["ventes"] | |
| 21 | + pred = np.exp(m["X"].values @ m["beta"]); obs = v["prix_vente"].values | |
| 22 | + comps = t["comparables"] | |
| 23 | + fig, ax = plt.subplots(figsize=(LARGEUR, 3.9)) | |
| 24 | + ax.scatter(pred, obs, s=9, color=palette.cat[0], alpha=0.45, linewidths=0, label="600 ventes synthétiques") | |
| 25 | + lo, hi = 150_000, 1_000_000 | |
| 26 | + ax.plot([lo, hi], [lo, hi], color=palette.ink2, lw=1.0, ls="--", label="Prix = valeur estimée") | |
| 27 | + ax.plot([lo, hi], [lo * 1.1, hi * 1.1], color=palette.grid, lw=0.8, ls=":"); ax.plot([lo, hi], [lo * 0.9, hi * 0.9], color=palette.grid, lw=0.8, ls=":") | |
| 28 | + ax.text(hi / 1.1 * 0.97, hi * 0.985, "+10 %", ha="right", va="top", fontsize=7.5, color=palette.ink2) | |
| 29 | + ax.text(hi * 0.985, hi * 0.9 * 0.985, "−10 %", ha="right", va="top", fontsize=7.5, color=palette.ink2) | |
| 30 | + ax.scatter(comps["valeur_estimee"], comps["prix_vente"], s=34, facecolors="none", edgecolors=palette.cat[1], linewidths=1.3, label="Comparables du chapitre 16", zorder=5) | |
| 31 | + for _, r in comps.iterrows(): | |
| 32 | + ax.annotate(r["id_vente"], (r["valeur_estimee"], r["prix_vente"]), xytext=(5, -9), textcoords="offset points", fontsize=7, color=palette.ink) | |
| 33 | + p = t["pred"] | |
| 34 | + ax.axvline(p["mediane"], color=palette.cat[2], lw=0.9, ls="-.") | |
| 35 | + ax.text(p["mediane"] * 1.01, lo * 1.15, "valeur estimée du sujet\n" + fmt_dollars(p["mediane"]), fontsize=7.5, color=palette.ink, ha="left") | |
| 36 | + ax.set_xlim(lo, hi); ax.set_ylim(lo, hi) | |
| 37 | + ax.xaxis.set_major_formatter(plt.FuncFormatter(fmt_dollars)); ax.yaxis.set_major_formatter(plt.FuncFormatter(fmt_dollars)) | |
| 38 | + ax.set_xlabel("Valeur estimée $\\hat V = \\exp(\\mathbf{x}'\\hat{\\beta})$ (médiane conditionnelle)"); ax.set_ylabel("Prix de vente observé $P$") | |
| 39 | + ax.legend(loc="upper left", fontsize=7.5) | |
| 40 | + return savefig(fig, "ch01/ch01-fig-prix-valeur.pdf") | |
| 41 | + | |
| 42 | +def fig_residus(t): | |
| 43 | + res = t["modele"]["residus"]; sigma = t["modele"]["sigma"] | |
| 44 | + ecarts = (np.exp(res) - 1) * 100 | |
| 45 | + fig, ax = plt.subplots(figsize=(LARGEUR, 3.2)) | |
| 46 | + bins = np.arange(-30, 32, 2.5) | |
| 47 | + ax.hist(ecarts, bins=bins, density=True, color=palette.cat[0], alpha=0.75, edgecolor=palette.surface, linewidth=0.5, label="Écarts observés (600 ventes)") | |
| 48 | + x = np.linspace(-30, 30, 400) | |
| 49 | + # densité de (exp(e) − 1)·100 avec e ~ N(0, σ²) | |
| 50 | + dens = norm.pdf(np.log(1 + x / 100), 0, sigma) / (1 + x / 100) / 100 | |
| 51 | + ax.plot(x, dens, color=palette.cat[1], lw=1.6, label=f"Loi log-normale ajustée ($\\sigma$ = {sigma:.3f})") | |
| 52 | + for q in (-10, 10): | |
| 53 | + ax.axvline(q, color=palette.ink2, lw=0.8, ls="--") | |
| 54 | + d = t["dispersion"] | |
| 55 | + ax.text(-29, ax.get_ylim()[1] * 0.80, f"{d['part_dans_pm10pct']*100:.1f} % des ventes\nà ±10 % de la\nvaleur estimée", ha="left", fontsize=8, color=palette.ink) | |
| 56 | + ax.set_xlabel("Écart entre le prix observé et la valeur estimée, $P/\\hat V - 1$ (%)"); ax.set_ylabel("Densité") | |
| 57 | + ax.legend(loc="upper right", fontsize=7.5) | |
| 58 | + return savefig(fig, "ch01/ch01-fig-residus.pdf") | |
| 59 | + | |
| 60 | +def fig_recherche(t): | |
| 61 | + s = t["recherche"]; centre = t["pred"]["mediane"]; P = c1.PARAMS_RECHERCHE | |
| 62 | + prix_rel = (s["prix"] / centre - 1) * 100; r_rel = (s["prix_reserve"] / centre - 1) * 100 | |
| 63 | + fig, (a1, a2) = plt.subplots(1, 2, figsize=(LARGEUR, 3.0), gridspec_kw={"width_ratios": [1.5, 1]}) | |
| 64 | + x = np.linspace(-25, 25, 500) | |
| 65 | + a1.plot(x, norm.pdf(np.log(1 + x / 100), 0, P["sigma_o"]) / (1 + x / 100) / 100, color=palette.cat[0], lw=1.5, label="Offres reçues (toutes)") | |
| 66 | + a1.hist(prix_rel, bins=np.arange(-5, 25, 1.0), density=True, color=palette.cat[1], alpha=0.7, edgecolor=palette.surface, linewidth=0.4, label="Prix acceptés (simulation)") | |
| 67 | + a1.axvline(r_rel, color=palette.ink2, lw=0.9, ls="--") | |
| 68 | + a1.text(r_rel + 7.5, a1.get_ylim()[1] * 0.62, f"prix de réserve\n+{r_rel:.1f} %", fontsize=7.5, color=palette.ink) | |
| 69 | + a1.set_xlabel("Écart au centre des enchères $m$ (%)"); a1.set_ylabel("Densité"); a1.legend(loc="upper left", fontsize=7) | |
| 70 | + delais = s["delai"]; mx = int(min(delais.max(), 24)) | |
| 71 | + vals = [np.mean(delais == k) for k in range(1, mx + 1)] | |
| 72 | + a2.bar(range(1, mx + 1), vals, color=palette.cat[0], width=0.8) | |
| 73 | + a2.axvline(s["delai_moyen"], color=palette.ink2, lw=0.9, ls="--") | |
| 74 | + a2.text(s["delai_moyen"] + 0.4, max(vals) * 0.9, f"moyenne\n{s['delai_moyen']:.1f} mois", fontsize=7.5, color=palette.ink) | |
| 75 | + a2.set_xlabel("Délai de vente (mois)"); a2.set_ylabel("Fréquence"); a2.set_xlim(0.3, mx + 0.7) | |
| 76 | + fig.tight_layout(w_pad=1.5) | |
| 77 | + return savefig(fig, "ch01/ch01-fig-recherche.pdf") | |
| 78 | + | |
| 79 | +def fig_rosen(): | |
| 80 | + """Schéma : fonctions d'enchère θ(z; u) concaves de deux acheteurs, fonctions d'offre φ(z; π) convexes de deux vendeurs, | |
| 81 | + enveloppe p(z) tangente aux deux familles. Formes fonctionnelles déclarées (purement illustratives).""" | |
| 82 | + z = np.linspace(0.5, 4.5, 400) | |
| 83 | + p = 100 + 60 * np.log(z) # enveloppe (fonction de prix hédonique) | |
| 84 | + dp = 60 / z | |
| 85 | + fig, ax = plt.subplots(figsize=(LARGEUR, 3.4)) | |
| 86 | + ax.plot(z, p, color=palette.ink, lw=2.0, label="Fonction de prix $p(z)$ (enveloppe)") | |
| 87 | + for i, z0 in enumerate((1.3, 3.2)): | |
| 88 | + p0, s0 = 100 + 60 * math.log(z0), 60 / z0 | |
| 89 | + theta = p0 + s0 * (z - z0) - 18 * (z - z0) ** 2 # enchère d'un acheteur : concave, tangente en z0 | |
| 90 | + phi = p0 + s0 * (z - z0) + 18 * (z - z0) ** 2 # offre d'un vendeur : convexe, tangente en z0 | |
| 91 | + mask = np.abs(z - z0) < 0.9 | |
| 92 | + ax.plot(z[mask], theta[mask], color=palette.cat[0], lw=1.3, ls="--", label="Enchères des acheteurs $\\theta(z; u)$" if i == 0 else None) | |
| 93 | + ax.plot(z[mask], phi[mask], color=palette.cat[1], lw=1.3, ls=":", label="Offres des vendeurs $\\phi(z; \\pi)$" if i == 0 else None) | |
| 94 | + ax.scatter([z0], [p0], color=palette.ink, s=22, zorder=5) | |
| 95 | + ax.annotate(f"appariement {i+1}", (z0, p0), xytext=(8, -14), textcoords="offset points", fontsize=7.5, color=palette.ink) | |
| 96 | + ax.set_xlabel("Quantité de l'attribut $z$ (p. ex. superficie)"); ax.set_ylabel("Prix") | |
| 97 | + ax.set_yticks([]); ax.set_xticks([]) | |
| 98 | + ax.text(4.45, 100 + 60 * math.log(4.45) - 6, "pente = prix implicite $\\partial p/\\partial z$", ha="right", fontsize=7.5, color=palette.ink2) | |
| 99 | + ax.legend(loc="lower right", fontsize=7.5) | |
| 100 | + return savefig(fig, "ch01/ch01-fig-rosen.pdf") | |
| 101 | + | |
| 102 | +def fig_sensibilite(t): | |
| 103 | + sig = np.linspace(0.02, 0.20, 100) | |
| 104 | + fig, (a1, a2) = plt.subplots(1, 2, figsize=(LARGEUR, 3.0)) | |
| 105 | + a1.plot(sig, (np.exp(1.96 * sig) - 1) * 100, color=palette.cat[0], lw=1.6, label="Borne haute (+)") | |
| 106 | + a1.plot(sig, (np.exp(-1.96 * sig) - 1) * 100, color=palette.cat[1], lw=1.6, label="Borne basse (−)") | |
| 107 | + a1.fill_between(sig, (np.exp(-1.96 * sig) - 1) * 100, (np.exp(1.96 * sig) - 1) * 100, color=palette.cat[0], alpha=0.08) | |
| 108 | + s0 = t["modele"]["sigma"] | |
| 109 | + a1.axvline(s0, color=palette.ink2, lw=0.8, ls="--"); a1.text(s0 + 0.004, -35, f"cas 1 : $\\sigma$ = {s0:.3f}", fontsize=7.5, color=palette.ink) | |
| 110 | + a1.set_xlabel("Dispersion $\\sigma$ (écart-type de $\\ln P$ à $\\mathbf{x}$ donné)"); a1.set_ylabel("Bornes de l'intervalle à 95 % (% de la valeur)") | |
| 111 | + a1.legend(loc="upper left", fontsize=7.5) | |
| 112 | + for d, col, lab in ((0.05, palette.cat[2], "> 5 %"), (0.10, palette.cat[0], "> 10 %"), (0.20, palette.cat[1], "> 20 %")): | |
| 113 | + a2.plot(sig, [c1.proba_ecart(s, d) * 100 for s in sig], color=col, lw=1.6, label=f"écart {lab}") | |
| 114 | + a2.axvline(s0, color=palette.ink2, lw=0.8, ls="--") | |
| 115 | + a2.set_xlabel("Dispersion $\\sigma$"); a2.set_ylabel("Probabilité qu'un prix s'écarte de la valeur (%)") | |
| 116 | + a2.legend(loc="upper left", fontsize=7.5) | |
| 117 | + fig.tight_layout(w_pad=1.5) | |
| 118 | + return savefig(fig, "ch01/ch01-fig-sensibilite-sigma.pdf") | |
| 119 | + | |
| 120 | +def main(): | |
| 121 | + setup() | |
| 122 | + t = c1.tout() | |
| 123 | + for f in (fig_prix_valeur(t), fig_residus(t), fig_recherche(t), fig_rosen(), fig_sensibilite(t)): | |
| 124 | + print("OK", f) | |
| 125 | + | |
| 126 | +if __name__ == "__main__": | |
| 127 | + main() | |
added
analysis/python/figures/ch02_decomposition.py
+42 −0
@@ -0,0 +1,42 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Figure ch02 — cas 3 (DONNÉES SYNTHÉTIQUES) : (a) revenu net d'exploitation projeté avec les baux en place (droit du locateur) | |
| 3 | +et libre de baux (loyers de marché dès l'année 1) ; la surface entre les deux est le revenu cédé aux locataires ; | |
| 4 | +(b) valeur agrégée des baux pour les locataires (V_pleine − V_locateur) selon le nombre de logements reloués par an et le taux. | |
| 5 | +Sortie : assets/figures/ch02/ch02-fig-decomposition-droits.pdf""" | |
| 6 | +import sys, pathlib | |
| 7 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 8 | +sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) | |
| 9 | +import numpy as np, matplotlib.pyplot as plt | |
| 10 | +from traite.style import setup, palette, savefig, LARGEUR, fmt_dollars | |
| 11 | +import ch02_droits as cas | |
| 12 | + | |
| 13 | + | |
| 14 | +def main(): | |
| 15 | + setup() | |
| 16 | + res = cas.main(verbose=False) | |
| 17 | + base, sens = res["base"], res["sensibilite"] | |
| 18 | + pc, pm = base["proj_contrat"], base["proj_marche"] | |
| 19 | + fig, (a, b) = plt.subplots(1, 2, figsize=(LARGEUR, 3.2), gridspec_kw={"width_ratios": [1.1, 1]}) | |
| 20 | + yrs = pc["exercice"].values | |
| 21 | + a.plot(yrs, pm["rne"] / 1000, color=palette.cat[1], marker="s", ms=3, label="libre de baux (loyers de marché)") | |
| 22 | + a.plot(yrs, pc["rne"] / 1000, color=palette.cat[0], marker="o", ms=3, label="baux en place (droit du locateur)") | |
| 23 | + a.fill_between(yrs, pc["rne"] / 1000, pm["rne"] / 1000, where=pm["rne"] >= pc["rne"], color=palette.cat[1], alpha=0.18, lw=0) | |
| 24 | + a.annotate("revenu cédé\naux locataires", xy=(yrs[1], (pc["rne"].iloc[1] + pm["rne"].iloc[1]) / 2000), xytext=(yrs[3] + 0.3, 96), | |
| 25 | + fontsize=7.5, color=palette.ink, arrowprops=dict(arrowstyle="-", color=palette.ink2, lw=0.6)) | |
| 26 | + a.set_xlabel("Exercice"); a.set_ylabel("Revenu net d'exploitation (milliers de $)"); a.set_xticks(yrs[::2]) | |
| 27 | + a.legend(loc="upper left", fontsize=7.4); a.set_title("(a) RNE projeté, deux faisceaux de droits", loc="left") | |
| 28 | + # (b) sensibilité | |
| 29 | + for k, r in enumerate(cas.TAUX): | |
| 30 | + s = sens[np.isclose(sens["taux"], r)].sort_values("rotation") | |
| 31 | + b.plot((s["rotation"] * 12).round(0), s["V_locataires"] / 1000, marker="o", ms=3.5, color=palette.cat[k], label=f"$r$ = {r*100:.2f} %") | |
| 32 | + b.set_yscale("log"); b.set_yticks([5, 10, 25, 50, 100, 250, 500]); b.set_yticklabels(["5", "10", "25", "50", "100", "250", "500"]) | |
| 33 | + b.set_xticks([0, 1, 2, 3, 6]); b.set_xlabel("Logements reloués au marché par an (sur 12)") | |
| 34 | + b.set_ylabel("$V_{\\mathrm{pleine}} - V_{\\mathrm{locateur}}$ (milliers de dollars, éch. log.)") | |
| 35 | + b.axvline(2, color=palette.ink2, lw=0.7, ls=":"); b.text(2.15, 200, "hypothèse\ndu cas 3", fontsize=7, color=palette.ink2) | |
| 36 | + b.legend(loc="lower left", fontsize=7.4); b.set_title("(b) Valeur agrégée des baux pour les locataires", loc="left") | |
| 37 | + fig.tight_layout(w_pad=2.2) | |
| 38 | + out = savefig(fig, "ch02/ch02-fig-decomposition-droits.pdf"); print("OK", out) | |
| 39 | + | |
| 40 | + | |
| 41 | +if __name__ == "__main__": | |
| 42 | + main() | |
added
analysis/python/figures/ch02_ecart_logements.py
+42 −0
@@ -0,0 +1,42 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Figure ch02 — cas 3 (DONNÉES SYNTHÉTIQUES) : (a) ratio loyer contractuel / loyer de marché selon l'ancienneté du locataire, | |
| 3 | +avec la loi de génération déclarée (0,98^ancienneté) ; (b) valeur du bail pour le locataire selon la durée de maintien attendue, | |
| 4 | +r = 7,25 %, pour quatre logements. Sortie : assets/figures/ch02/ch02-fig-ecart-logements.pdf""" | |
| 5 | +import sys, pathlib | |
| 6 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 7 | +sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) | |
| 8 | +import numpy as np, matplotlib.pyplot as plt | |
| 9 | +from traite.style import setup, palette, savefig, LARGEUR, fmt_dollars | |
| 10 | +import ch02_droits as cas | |
| 11 | + | |
| 12 | + | |
| 13 | +def main(): | |
| 14 | + setup() | |
| 15 | + res = cas.main(verbose=False) | |
| 16 | + e = res["ecarts"]; r = res["d"]["hyp"]["taux_actualisation"] | |
| 17 | + fig, (a, b) = plt.subplots(1, 2, figsize=(LARGEUR, 3.1), gridspec_kw={"width_ratios": [1, 1.1]}) | |
| 18 | + # (a) ratio vs ancienneté | |
| 19 | + x = np.linspace(0, 20, 100) | |
| 20 | + a.plot(x, 0.98 ** x, color=palette.ink2, lw=1.0, ls="--", label="loi de génération $0{,}98^{\\,a}$") | |
| 21 | + a.scatter(e["anciennete"], e["ratio_contrat_marche"], color=palette.cat[0], s=28, zorder=3, label="logements du cas 3") | |
| 22 | + for anc, grp in e.groupby("anciennete"): # une étiquette par ancienneté (plusieurs logements partagent la même) | |
| 23 | + lab = ", ".join(str(int(x)) for x in sorted(grp["logement"])) | |
| 24 | + a.annotate(lab, (anc, grp["ratio_contrat_marche"].mean()), xytext=(anc + 0.45, grp["ratio_contrat_marche"].mean() + 0.004), fontsize=6.8, color=palette.ink) | |
| 25 | + a.set_xlabel("Ancienneté du locataire (années, au 1$^{\\rm er}$ sept. 2026)"); a.set_ylabel("Loyer contractuel / loyer de marché") | |
| 26 | + a.set_ylim(0.62, 1.02); a.set_xlim(-0.5, 20); a.legend(loc="lower left") | |
| 27 | + a.set_title("(a) Écart au marché et ancienneté", loc="left") | |
| 28 | + # (b) valeur du bail pour le locataire selon n | |
| 29 | + n = np.arange(1, 11) | |
| 30 | + for k, lg in enumerate([103, 102, 302, 101]): | |
| 31 | + row = e.loc[e["logement"] == lg].iloc[0] | |
| 32 | + va = [cas.valeur_bail_locataire(row["ecart_annuel"], int(j), r) for j in n] | |
| 33 | + b.plot(n, np.array(va) / 1000, marker="o", ms=3.5, color=palette.cat[k], label=f"log. {lg} ({row['ecart_mensuel']:.0f} $/mois)") | |
| 34 | + b.set_xlabel("Durée de maintien dans les lieux $n$ (années)"); b.set_ylabel("Valeur du bail pour le locataire (milliers de $)") | |
| 35 | + b.set_xticks(n); b.legend(loc="upper left", fontsize=7.2) | |
| 36 | + b.set_title("(b) $\\sum_{t \\leq n} (L^m_t - L^c_t)/(1{,}0725)^t$", loc="left") | |
| 37 | + fig.tight_layout(w_pad=2.2) | |
| 38 | + out = savefig(fig, "ch02/ch02-fig-ecart-logements.pdf"); print("OK", out) | |
| 39 | + | |
| 40 | + | |
| 41 | +if __name__ == "__main__": | |
| 42 | + main() | |
added
analysis/python/figures/ch06_emphyteose.py
+34 −0
@@ -0,0 +1,34 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Figure ch06 — emphytéose sur un immeuble locatif dont le RNE est celui du cas 3 (DONNÉES SYNTHÉTIQUES, hypothèses déclarées) : | |
| 3 | +décomposition de la valeur de la pleine propriété entre le droit de l'emphytéote et le droit du propriétaire (rentes + reprise) | |
| 4 | +selon la durée restante, avec les seuils de 40 ans (D-15.1, art. 1) et de 50 ans (art. 1040 C.c.Q.). | |
| 5 | +Sortie : assets/figures/ch06/ch06-fig-emphyteose.pdf""" | |
| 6 | +import sys, pathlib | |
| 7 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 8 | +sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) | |
| 9 | +import numpy as np, matplotlib.pyplot as plt | |
| 10 | +from traite.style import setup, palette, savefig, LARGEUR, fmt_dollars | |
| 11 | +import ch06_demembrements as cas | |
| 12 | + | |
| 13 | + | |
| 14 | +def main(): | |
| 15 | + setup() | |
| 16 | + noi = cas.rne_cas3() | |
| 17 | + n = np.arange(1, 101) | |
| 18 | + res = [cas.emphyteose(noi, cas.RENTE_EMPH, int(k)) for k in n] | |
| 19 | + emph = np.array([r["V_emph"] for r in res]) / 1e6; rentes = np.array([r["V_rentes"] for r in res]) / 1e6 | |
| 20 | + reprise = np.array([r["V_reprise"] for r in res]) / 1e6; V = res[0]["V_pleine"] / 1e6 | |
| 21 | + fig, ax = plt.subplots(figsize=(LARGEUR, 3.2)) | |
| 22 | + ax.stackplot(n, emph, rentes, reprise, colors=[palette.cat[0], palette.cat[1], palette.cat[2]], alpha=0.85, | |
| 23 | + labels=["droit de l'emphytéote (RNE − rente)", "propriétaire : rentes actualisées", "propriétaire : reprise de l'immeuble au terme"]) | |
| 24 | + ax.axhline(V, color=palette.ink, lw=0.8); ax.text(99, V + 0.03, f"pleine propriété {fmt_dollars(V*1e6)}", ha="right", va="bottom", fontsize=7.5) | |
| 25 | + for seuil, txt in ((40, "40 ans : bail assimilé à un transfert\n(D-15.1, art. 1)"), (50, "50 ans : copropriété divise\npossible (art. 1040 C.c.Q.)")): | |
| 26 | + ax.axvline(seuil, color=palette.ink2, ls=":", lw=1); ax.text(seuil + 1, 0.12, txt, fontsize=7, color=palette.ink2, va="bottom") | |
| 27 | + ax.set_xlabel("Durée restante de l'emphytéose (années)"); ax.set_ylabel("Valeur (millions de $)"); ax.set_xlim(1, 100); ax.set_ylim(0, V * 1.12) | |
| 28 | + ax.legend(loc="upper left", fontsize=7.5, ncol=1, bbox_to_anchor=(0.0, 0.98)); ax.grid(axis="x", visible=False) | |
| 29 | + fig.tight_layout() | |
| 30 | + out = savefig(fig, "ch06/ch06-fig-emphyteose.pdf"); print("OK", out) | |
| 31 | + | |
| 32 | + | |
| 33 | +if __name__ == "__main__": | |
| 34 | + main() | |
added
analysis/python/figures/ch06_usufruit.py
+39 −0
@@ -0,0 +1,39 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Figure ch06 — usufruit viager sur la maison du cas 1 (DONNÉES SYNTHÉTIQUES, table de mortalité synthétique) : | |
| 3 | +(a) courbes de survie tp_x de la table synthétique pour trois âges ; (b) part de la nue-propriété NP/V selon l'âge de | |
| 4 | +l'usufruitier pour trois taux d'actualisation, comparée à l'approximation par un terme fixe égal à l'espérance de vie. | |
| 5 | +Sortie : assets/figures/ch06/ch06-fig-usufruit.pdf""" | |
| 6 | +import sys, pathlib | |
| 7 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 8 | +sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) | |
| 9 | +import numpy as np, matplotlib.pyplot as plt | |
| 10 | +from traite.style import setup, palette, savefig, LARGEUR | |
| 11 | +import ch06_demembrements as cas | |
| 12 | + | |
| 13 | + | |
| 14 | +def main(): | |
| 15 | + setup() | |
| 16 | + V = cas.V_ARRONDI | |
| 17 | + fig, (a, b) = plt.subplots(1, 2, figsize=(LARGEUR, 3.1), gridspec_kw={"width_ratios": [1, 1.15]}) | |
| 18 | + for i, x in enumerate((55, 65, 75)): | |
| 19 | + t = np.arange(0, cas.OMEGA - x + 1) | |
| 20 | + a.plot(x + t, [cas.survie(x, k) for k in t], color=palette.cat[i], label=f"usufruitier de {x} ans") | |
| 21 | + a.set_xlabel("Âge atteint (années)"); a.set_ylabel("Probabilité de survie $_t p_x$"); a.set_ylim(0, 1.02); a.set_xlim(55, 110) | |
| 22 | + a.legend(loc="lower left"); a.set_title("(a) Table de mortalité synthétique", loc="left") | |
| 23 | + ages = np.arange(50, 91) | |
| 24 | + for i, r in enumerate((0.04, 0.05, 0.06)): | |
| 25 | + b.plot(ages, [cas.usufruit_viager(V, int(x), r)["part_NP"] for x in ages], color=palette.cat[i], label=f"viager, $r$ = {r*100:.0f} %") | |
| 26 | + approx = [] | |
| 27 | + for x in ages: | |
| 28 | + n = int(round(cas.esperance_vie(int(x)))) | |
| 29 | + approx.append(cas.usufruit_terme(V, n, 0.05)["part_NP"]) | |
| 30 | + b.plot(ages, approx, color=palette.ink2, ls="--", lw=1.2, label="terme fixe = espérance de vie, $r$ = 5 %") | |
| 31 | + b.set_xlabel("Âge de l'usufruitier à la date d'évaluation"); b.set_ylabel("Nue-propriété / pleine propriété") | |
| 32 | + b.set_ylim(0.2, 1.0); b.yaxis.set_major_formatter(plt.FuncFormatter(lambda y, p: f"{y*100:.0f} %")) | |
| 33 | + b.legend(loc="upper left", fontsize=7.5); b.set_title("(b) Part de la nue-propriété ($g$ = 1,5 %)", loc="left") | |
| 34 | + fig.tight_layout(w_pad=2.0) | |
| 35 | + out = savefig(fig, "ch06/ch06-fig-usufruit.pdf"); print("OK", out) | |
| 36 | + | |
| 37 | + | |
| 38 | +if __name__ == "__main__": | |
| 39 | + main() | |
added
analysis/python/figures/ch07_charges.py
+33 −0
@@ -0,0 +1,33 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Figure ch07 — répartition des charges communes annuelles entre les huit fractions (DONNÉES SYNTHÉTIQUES). | |
| 3 | +Sortie : assets/figures/ch07/ch07-fig-repartition-charges.pdf""" | |
| 4 | +import sys, pathlib | |
| 5 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 6 | +sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) | |
| 7 | +import numpy as np, matplotlib.pyplot as plt | |
| 8 | +from traite.style import setup, palette, savefig, LARGEUR, fmt_dollars | |
| 9 | +import ch07_servitudes_copro as cas | |
| 10 | + | |
| 11 | +def main(): | |
| 12 | + setup() | |
| 13 | + qp = cas.quotes_parts(); A = cas.cotisation_seuil(); rep = cas.repartition_charges(A, qp) | |
| 14 | + noms = [f"{n} ({s:.0f} m², q = {q:.4f})" for n, s, q in zip(qp["noms"], qp["superficies"], qp["q"])] | |
| 15 | + blocs = [("Charges courantes (q_i × budget)", rep["par_fraction_courantes"], palette.cat[0]), | |
| 16 | + ("Fonds de prévoyance (q_i × A*)", rep["par_fraction_fonds"], palette.cat[1]), | |
| 17 | + ("Fonds d'auto assurance (q_i)", rep["par_fraction_auto"], palette.cat[2]), | |
| 18 | + ("Stationnement à usage restreint (part égale)", rep["par_fraction_restreint"], palette.cat[3])] | |
| 19 | + y = np.arange(len(noms)); gauche = np.zeros(len(noms)) | |
| 20 | + fig, ax = plt.subplots(figsize=(LARGEUR, 3.4)) | |
| 21 | + for lab, vals, col in blocs: | |
| 22 | + ax.barh(y, vals, left=gauche, color=col, height=0.66, label=lab); gauche += vals | |
| 23 | + for yi, tot in zip(y, rep["par_fraction_total"]): | |
| 24 | + ax.text(tot + 150, yi, f"{tot:,.0f} $/an".replace(",", " "), va="center", fontsize=7.5, color=palette.ink) | |
| 25 | + ax.set_yticks(y); ax.set_yticklabels(noms); ax.invert_yaxis() | |
| 26 | + ax.xaxis.set_major_formatter(plt.FuncFormatter(fmt_dollars)); ax.set_xlim(0, rep["par_fraction_total"].max() * 1.25) | |
| 27 | + ax.set_xlabel("Charges annuelles de la fraction (dollars de 2026, données synthétiques)") | |
| 28 | + ax.grid(axis="y", visible=False) | |
| 29 | + ax.legend(loc="upper center", bbox_to_anchor=(0.45, -0.2), ncol=2, fontsize=7.5) | |
| 30 | + out = savefig(fig, "ch07/ch07-fig-repartition-charges.pdf"); print("OK", out) | |
| 31 | + | |
| 32 | +if __name__ == "__main__": | |
| 33 | + main() | |
added
analysis/python/figures/ch07_fonds.py
+31 −0
@@ -0,0 +1,31 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Figure ch07 — trajectoire du fonds de prévoyance sur 25 ans selon trois cotisations (DONNÉES SYNTHÉTIQUES). | |
| 3 | +Sortie : assets/figures/ch07/ch07-fig-fonds-prevoyance.pdf""" | |
| 4 | +import sys, pathlib | |
| 5 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 6 | +sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) | |
| 7 | +import numpy as np, matplotlib.pyplot as plt | |
| 8 | +from traite.style import setup, palette, savefig, LARGEUR, fmt_dollars | |
| 9 | +import ch07_servitudes_copro as cas | |
| 10 | + | |
| 11 | +def main(): | |
| 12 | + setup() | |
| 13 | + A = cas.cotisation_seuil(); regle = 0.005 * cas.VALEUR_RECONSTRUCTION | |
| 14 | + t_act, t_regle, t_star = cas.trajectoire(cas.COTISATION_ACTUELLE), cas.trajectoire(regle), cas.trajectoire(A) | |
| 15 | + ans = np.arange(0, cas.HORIZON + 1) + 2026 | |
| 16 | + fig, ax = plt.subplots(figsize=(LARGEUR, 3.6)) | |
| 17 | + ax.bar(ans[1:], -t_star["D"][1:], color=palette.grid, width=0.7, label="Réparations majeures prévues (dépenses, indexées à 2,5 %)") | |
| 18 | + ax.plot(ans, t_act["F"], color=palette.cat[3], lw=1.8, label=f"Cotisation actuelle : {cas.COTISATION_ACTUELLE:,.0f} $ (indexée)".replace(",", " ")) | |
| 19 | + ax.plot(ans, t_regle["F"], color=palette.cat[2], lw=1.8, ls="--", label=f"Règle du 0,5 % de la valeur de reconstruction : {regle:,.0f} $".replace(",", " ")) | |
| 20 | + ax.plot(ans, t_star["F"], color=palette.cat[0], lw=2.0, label=f"Cotisation de seuil A* : {A:,.0f} $ (indexée)".replace(",", " ")) | |
| 21 | + ax.axhline(0, color=palette.ink2, lw=0.8) | |
| 22 | + ax.yaxis.set_major_formatter(plt.FuncFormatter(fmt_dollars)) | |
| 23 | + ax.set_xlabel("Année"); ax.set_ylabel("Solde du fonds en fin d'année") | |
| 24 | + ax.set_xlim(2025.5, 2051.5) | |
| 25 | + ax.legend(loc="upper left", fontsize=7.2) | |
| 26 | + ax.annotate("an 14 : fenêtres\n(solde A* = 0)", xy=(2039, 0), xytext=(2041.5, 120000), fontsize=7.5, color=palette.ink2, | |
| 27 | + arrowprops=dict(arrowstyle="-", color=palette.ink2, lw=0.7)) | |
| 28 | + out = savefig(fig, "ch07/ch07-fig-fonds-prevoyance.pdf"); print("OK", out) | |
| 29 | + | |
| 30 | +if __name__ == "__main__": | |
| 31 | + main() | |
added
analysis/python/figures/ch07_paires.py
+35 −0
@@ -0,0 +1,35 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Figure ch07 — ventes appariées : prix sans et avec servitude de passage pour 12 paires (DONNÉES SYNTHÉTIQUES, graine 20261031). | |
| 3 | +Sortie : assets/figures/ch07/ch07-fig-paires-servitude.pdf""" | |
| 4 | +import sys, pathlib | |
| 5 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 6 | +sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) | |
| 7 | +import numpy as np, matplotlib.pyplot as plt | |
| 8 | +from traite.style import setup, palette, savefig, LARGEUR, fmt_dollars | |
| 9 | +import ch07_servitudes_copro as cas | |
| 10 | + | |
| 11 | +def main(): | |
| 12 | + setup() | |
| 13 | + p = cas.paires_servitude() | |
| 14 | + ordre = np.argsort(p["sans"]) | |
| 15 | + sans, avec = p["sans"][ordre], p["avec"][ordre] | |
| 16 | + y = np.arange(len(sans)) | |
| 17 | + fig, ax = plt.subplots(figsize=(LARGEUR, 3.6)) | |
| 18 | + for yi, s, a in zip(y, sans, avec): | |
| 19 | + ax.plot([a, s], [yi, yi], color=palette.grid, lw=2.2, zorder=1) | |
| 20 | + ax.scatter(sans, y, color=palette.cat[0], s=34, zorder=3, label="Lot sans servitude (fonds libre)") | |
| 21 | + ax.scatter(avec, y, color=palette.cat[1], s=34, zorder=3, marker="D", label="Lot grevé d'une servitude de passage") | |
| 22 | + for yi, s, a in zip(y, sans, avec): | |
| 23 | + ax.text(s + 4000, yi, f"{a - s:+,.0f} $".replace(",", " "), va="center", ha="left", fontsize=7.5, color=palette.ink2) | |
| 24 | + ax.set_yticks(y); ax.set_yticklabels([f"paire {int(i) + 1}" for i in ordre]) | |
| 25 | + ax.xaxis.set_major_formatter(plt.FuncFormatter(fmt_dollars)) | |
| 26 | + ax.set_xlim(avec.min() - 15000, sans.max() + 60000) | |
| 27 | + ax.set_xlabel("Prix de vente (dollars de 2026, données synthétiques)") | |
| 28 | + ax.grid(axis="y", visible=False) | |
| 29 | + ax.set_title((f"Différence moyenne {p['moyenne']:,.0f} \\$ (IC 95 % : {p['ic95'][0]:,.0f} à {p['ic95'][1]:,.0f} \\$), soit {p['effet_relatif_moyen']*100:.1f} % du prix").replace(",", " ").replace(".", ","), | |
| 30 | + loc="left", fontsize=8.5) | |
| 31 | + ax.legend(loc="lower right", fontsize=7.5) | |
| 32 | + out = savefig(fig, "ch07/ch07-fig-paires-servitude.pdf"); print("OK", out) | |
| 33 | + | |
| 34 | +if __name__ == "__main__": | |
| 35 | + main() | |
added
analysis/python/figures/ch07_sensibilite.py
+27 −0
@@ -0,0 +1,27 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Figure ch07 — sensibilité de la cotisation de seuil A* à l'inflation des coûts et au rendement du fonds (DONNÉES SYNTHÉTIQUES). | |
| 3 | +Sortie : assets/figures/ch07/ch07-fig-sensibilite-cotisation.pdf""" | |
| 4 | +import sys, pathlib | |
| 5 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 6 | +sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) | |
| 7 | +import matplotlib.pyplot as plt | |
| 8 | +from traite.style import setup, palette, savefig, LARGEUR, fmt_dollars | |
| 9 | +import ch07_servitudes_copro as cas | |
| 10 | + | |
| 11 | +def main(): | |
| 12 | + setup() | |
| 13 | + s = cas.sensibilite_cotisation(); regle = 0.005 * cas.VALEUR_RECONSTRUCTION | |
| 14 | + fig, ax = plt.subplots(figsize=(LARGEUR, 3.2)) | |
| 15 | + for i, (r, row) in enumerate(s["grille"].items()): | |
| 16 | + ax.plot([p * 100 for p in s["inflations"]], row, marker="o", color=palette.cat[i], label=f"rendement du fonds r = {r*100:.0f} %") | |
| 17 | + ax.axhline(regle, color=palette.ink2, lw=0.9, ls=":") | |
| 18 | + ax.text(1.52, regle + 250, "règle du 0,5 % de la valeur de reconstruction (21 000 $)", fontsize=7.5, color=palette.ink2) | |
| 19 | + ax.axhline(cas.COTISATION_ACTUELLE, color=palette.cat[3], lw=0.9, ls=":") | |
| 20 | + ax.text(1.52, cas.COTISATION_ACTUELLE + 250, "cotisation actuelle (12 000 $)", fontsize=7.5, color=palette.cat[3]) | |
| 21 | + ax.set_xlabel("Hausse annuelle des coûts de construction π (%)"); ax.set_ylabel("Cotisation de seuil A* (an 1)") | |
| 22 | + ax.yaxis.set_major_formatter(plt.FuncFormatter(fmt_dollars)); ax.set_ylim(10000, 26000) | |
| 23 | + ax.legend(loc="lower right", fontsize=7.5) | |
| 24 | + out = savefig(fig, "ch07/ch07-fig-sensibilite-cotisation.pdf"); print("OK", out) | |
| 25 | + | |
| 26 | +if __name__ == "__main__": | |
| 27 | + main() | |
added
analysis/python/figures/ch08_carte_zones.py
+42 −0
@@ -0,0 +1,42 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Figure ch08 — carte synthétique des zones de « Val-Fictif » (DONNÉES SYNTHÉTIQUES) : 400 lots, cinq zones, | |
| 3 | +îlot rezoné H1 → H2 (hachures) et ventes de l'îlot postérieures au rezonage (points). | |
| 4 | +Sortie : assets/figures/ch08/ch08-fig-carte-zones.pdf""" | |
| 5 | +import sys, pathlib | |
| 6 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 7 | +sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) | |
| 8 | +import matplotlib.pyplot as plt | |
| 9 | +from matplotlib.patches import Rectangle, Patch | |
| 10 | +from matplotlib.lines import Line2D | |
| 11 | +from traite.style import setup, palette, savefig, LARGEUR | |
| 12 | +import ch08_zonage as cas | |
| 13 | + | |
| 14 | +ZONES = {"H1": ("H1 — habitation unifamiliale", 0), "H2": ("H2 — habitation 2 à 4 logements", 1), "H3": ("H3 — habitation multifamiliale", 2), | |
| 15 | + "C": ("C — commerce et usages mixtes", 3), "P": ("P — parc et institutionnel", 4)} | |
| 16 | + | |
| 17 | + | |
| 18 | +def main(): | |
| 19 | + setup() | |
| 20 | + lots, ventes = cas.charger_ou_generer(ecrire=False) | |
| 21 | + fig, ax = plt.subplots(figsize=(LARGEUR, 3.9)) | |
| 22 | + for _, L in lots.iterrows(): | |
| 23 | + c = palette.cat[ZONES[L["zone_initiale"]][1]] | |
| 24 | + ax.add_patch(Rectangle((L["x"], L["y"]), 1, 1, facecolor=c, edgecolor=palette.surface, linewidth=0.5, alpha=0.85)) | |
| 25 | + if L["rezone"]: | |
| 26 | + ax.add_patch(Rectangle((L["x"], L["y"]), 1, 1, facecolor="none", edgecolor=palette.ink, hatch="////", linewidth=0.0)) | |
| 27 | + # contour de l'îlot rezoné | |
| 28 | + ax.add_patch(Rectangle((7, 8), 3, 12, facecolor="none", edgecolor=palette.ink, linewidth=1.4)) | |
| 29 | + tr = ventes[ventes["traite_post"]].merge(lots[["id_lot", "x", "y"]], on="id_lot") | |
| 30 | + ax.scatter(tr["x"] + 0.5, tr["y"] + 0.5, s=14, color=palette.ink, zorder=5, linewidths=0) | |
| 31 | + ax.text(8.5, 20.35, "îlot rezoné H1 → H2\n(1er janv. 2025)", ha="center", va="bottom", fontsize=7.5, color=palette.ink) | |
| 32 | + ax.text(10, 1.5, "boulevard (zone C)", ha="center", va="center", fontsize=7.5, color=palette.surface, fontweight="bold") | |
| 33 | + ax.set_xlim(0, 20); ax.set_ylim(0, 22.2); ax.set_aspect("equal"); ax.axis("off") | |
| 34 | + handles = [Patch(facecolor=palette.cat[i], label=lab) for lab, i in ZONES.values()] | |
| 35 | + handles += [Patch(facecolor="none", edgecolor=palette.ink, hatch="////", label="lots rezonés (traités)"), | |
| 36 | + Line2D([0], [0], marker="o", color="none", markerfacecolor=palette.ink, markersize=4, label="ventes de l'îlot après T0")] | |
| 37 | + ax.legend(handles=handles, loc="center left", bbox_to_anchor=(1.01, 0.5), fontsize=7.5, frameon=False) | |
| 38 | + savefig(fig, "ch08/ch08-fig-carte-zones.pdf") | |
| 39 | + | |
| 40 | + | |
| 41 | +if __name__ == "__main__": | |
| 42 | + main() | |
added
analysis/python/figures/ch08_option_sensibilite.py
+39 −0
@@ -0,0 +1,39 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Figure ch08 — sensibilité des effets de valeur du zonage (DONNÉES SYNTHÉTIQUES / hypothèses déclarées) : | |
| 3 | +(a) prime espérée d'un rezonage probable selon la probabilité p et le délai T (grille, en milliers de $) ; | |
| 4 | +(b) valeur du surplus de revenu protégé par droits acquis V_Δ = ΔRNE/(R+λ) selon le taux de risque λ, pour trois R. | |
| 5 | +Sortie : assets/figures/ch08/ch08-fig-option-sensibilite.pdf""" | |
| 6 | +import sys, pathlib | |
| 7 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 8 | +sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) | |
| 9 | +import numpy as np, matplotlib.pyplot as plt | |
| 10 | +from traite.style import setup, palette, savefig, LARGEUR | |
| 11 | +import ch08_zonage as cas | |
| 12 | + | |
| 13 | + | |
| 14 | +def main(): | |
| 15 | + setup() | |
| 16 | + res = cas.exemple_complet() | |
| 17 | + E = cas.EXEMPLE | |
| 18 | + probs = np.array([0.10, 0.25, 0.40, 0.50, 0.60, 0.75, 0.90, 1.00]); delais = np.array([0.5, 1.0, 1.5, 2.0, 3.0, 4.0]) | |
| 19 | + G = cas.grille_attendue(E["lot"]["v_actuel"], res["redev"]["valeur_residuelle"], probs, delais, E["option"]["r"]) / 1000 | |
| 20 | + fig, (a, b) = plt.subplots(1, 2, figsize=(LARGEUR, 3.2), gridspec_kw={"width_ratios": [1.15, 1]}) | |
| 21 | + im = a.imshow(G, cmap=palette.cmap_seq(), aspect="auto", origin="lower") | |
| 22 | + a.set_xticks(range(len(delais))); a.set_xticklabels([f"{t:g}" for t in delais]); a.set_yticks(range(len(probs))); a.set_yticklabels([f"{p*100:.0f} %" for p in probs]) | |
| 23 | + a.set_xlabel("Délai T avant l'autorisation (années)"); a.set_ylabel("Probabilité p d'obtenir le changement"); a.grid(False) | |
| 24 | + for i in range(len(probs)): | |
| 25 | + for j in range(len(delais)): | |
| 26 | + a.text(j, i, f"{G[i, j]:.0f}", ha="center", va="center", fontsize=7, color="white" if G[i, j] > G.mean() else palette.ink) | |
| 27 | + a.set_title("(a) prime espérée du rezonage (k$)", loc="left") | |
| 28 | + lam = np.linspace(0, 0.08, 81); dn = res["delta_noi"] | |
| 29 | + for k, R in enumerate([0.045, 0.055, 0.070]): | |
| 30 | + b.plot(lam * 100, [cas.valeur_surplus_derogatoire(dn, R, l) / 1000 for l in lam], color=palette.cat[k], label=f"R = {R*100:.1f} %") | |
| 31 | + b.plot(lam * 100, [cas.valeur_surplus_horizon(dn, 0.055, l, E["droits_acquis"]["horizon"]) / 1000 for l in lam], color=palette.cat[1], linestyle="--", label="R = 5,5 %, horizon 25 ans") | |
| 32 | + b.set_xlabel("Taux de risque d'extinction λ (% par an)"); b.set_ylabel("V_Δ (milliers de $)") | |
| 33 | + b.set_title("(b) surplus dérogatoire : ΔRNE = 3 300 $", loc="left"); b.legend(fontsize=7, loc="upper right") | |
| 34 | + fig.tight_layout() | |
| 35 | + savefig(fig, "ch08/ch08-fig-option-sensibilite.pdf") | |
| 36 | + | |
| 37 | + | |
| 38 | +if __name__ == "__main__": | |
| 39 | + main() | |
added
analysis/python/figures/ch08_rezonage_paires.py
+46 −0
@@ -0,0 +1,46 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Figure ch08 — effet d'un rezonage mesuré par ventes appariées et par doubles différences (DONNÉES SYNTHÉTIQUES). | |
| 3 | +(a) différence traité − témoin ajusté (% du témoin) selon l'âge de la maison, avec moyenne et médiane ; | |
| 4 | +(b) ln P moyen des ventes H1 traitées et témoins avant/après T0 (schéma 2 × 2) et contrefactuel. | |
| 5 | +Sortie : assets/figures/ch08/ch08-fig-rezonage-paires.pdf""" | |
| 6 | +import sys, pathlib | |
| 7 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 8 | +sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) | |
| 9 | +import numpy as np, matplotlib.pyplot as plt | |
| 10 | +from traite.style import setup, palette, savefig, LARGEUR | |
| 11 | +import ch08_zonage as cas | |
| 12 | + | |
| 13 | + | |
| 14 | +def main(): | |
| 15 | + setup() | |
| 16 | + lots, ventes = cas.charger_ou_generer(ecrire=False) | |
| 17 | + paires = cas.apparier(ventes); rp = cas.resume_paires(paires); d = cas.did(ventes) | |
| 18 | + fig, (a, b) = plt.subplots(1, 2, figsize=(LARGEUR, 3.1), gridspec_kw={"width_ratios": [1.25, 1]}) | |
| 19 | + x = paires["age_traite"].to_numpy(); y = paires["difference_pct"].to_numpy() * 100 | |
| 20 | + a.scatter(x, y, s=18, color=palette.cat[0], linewidths=0, zorder=3) | |
| 21 | + xx = np.linspace(x.min(), x.max(), 50); pente, orig = np.polyfit(x, y, 1) | |
| 22 | + a.plot(xx, orig + pente * xx, color=palette.cat[0], linewidth=1.2) | |
| 23 | + a.plot(xx, (np.exp([cas.prime_rezonage(v) for v in xx]) - 1) * 100, color=palette.ink2, linestyle="--", linewidth=1.0) | |
| 24 | + a.axhline(rp["moyenne_pct"] * 100, color=palette.cat[1], linewidth=1.0) | |
| 25 | + a.axhline(rp["mediane_pct"] * 100, color=palette.cat[1], linewidth=1.0, linestyle=":") | |
| 26 | + a.text(x.max(), rp["moyenne_pct"] * 100 + 1.2, f"moyenne {rp['moyenne_pct']*100:.1f} %", ha="right", fontsize=7.5, color=palette.ink) | |
| 27 | + a.text(x.max(), rp["mediane_pct"] * 100 - 3.2, f"médiane {rp['mediane_pct']*100:.1f} %", ha="right", fontsize=7.5, color=palette.ink) | |
| 28 | + a.text(x.max(), y.min() + 1.0, "tireté : prime injectée\n(vérité du générateur)", ha="right", fontsize=7, color=palette.ink2) | |
| 29 | + a.set_xlabel("Âge de la maison traitée (années)"); a.set_ylabel("Traité − témoin ajusté (% du témoin)") | |
| 30 | + a.set_title(f"(a) {rp['n']} paires appariées", loc="left") | |
| 31 | + m = d["moyennes_lnP"] | |
| 32 | + xs = [0, 1] | |
| 33 | + b.plot(xs, [m[(False, False)], m[(False, True)]], marker="o", color=palette.cat[2], label="témoins (reste de H1)") | |
| 34 | + b.plot(xs, [m[(True, False)], m[(True, True)]], marker="o", color=palette.cat[0], label="traités (îlot rezoné)") | |
| 35 | + cf = m[(True, False)] + (m[(False, True)] - m[(False, False)]) | |
| 36 | + b.plot(xs, [m[(True, False)], cf], marker="o", markerfacecolor="none", color=palette.cat[0], linestyle="--", label="contrefactuel des traités") | |
| 37 | + b.annotate("", xy=(1, m[(True, True)]), xytext=(1, cf), arrowprops=dict(arrowstyle="<->", color=palette.ink, lw=1.0)) | |
| 38 | + b.text(1.03, (m[(True, True)] + cf) / 2, f"δ = {d['delta_2x2']:.3f}\n(2 × 2)", fontsize=7.5, va="center", color=palette.ink) | |
| 39 | + b.set_xticks(xs); b.set_xticklabels(["avant T0", "après T0"]); b.set_ylabel("ln P moyen (ventes H1)") | |
| 40 | + b.set_xlim(-0.3, 1.5); b.set_title("(b) doubles différences", loc="left"); b.legend(loc="upper left", fontsize=7) | |
| 41 | + fig.tight_layout() | |
| 42 | + savefig(fig, "ch08/ch08-fig-rezonage-paires.pdf") | |
| 43 | + | |
| 44 | + | |
| 45 | +if __name__ == "__main__": | |
| 46 | + main() | |
added
analysis/python/figures/ch10_figures.py
+124 −0
@@ -0,0 +1,124 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Chapitre 10 — figures Matplotlib : | |
| 3 | + ch10-fig-chronologie-cadres.pdf chronologie documentaire des cadres normatifs (dates du registre C) ; | |
| 4 | + ch10-fig-matrice-normes.pdf statistiques descriptives de la matrice des dispositions (sources/normes-C.csv) ; | |
| 5 | + ch10-fig-grille-controle.pdf grille de détection des douze éléments dans quatre extraits FICTIFS. | |
| 6 | +Usage (racine du dépôt) : PYTHONPATH=analysis/python python3 analysis/python/figures/ch10_figures.py""" | |
| 7 | +from __future__ import annotations | |
| 8 | +import sys, pathlib, datetime as dt | |
| 9 | +import matplotlib.pyplot as plt | |
| 10 | +import matplotlib.dates as mdates | |
| 11 | +from matplotlib.lines import Line2D | |
| 12 | +from matplotlib.patches import Patch | |
| 13 | + | |
| 14 | +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1])) | |
| 15 | +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases")) | |
| 16 | +from traite.style import setup, palette, savefig, LARGEUR | |
| 17 | +import ch10_matrice_normes as mn | |
| 18 | +import ch10_controle_rapport as cr | |
| 19 | + | |
| 20 | +def fig_chronologie(ch): | |
| 21 | + fig, ax = plt.subplots(figsize=(LARGEUR, 4.0)) | |
| 22 | + ch = ch.copy() | |
| 23 | + ch["ordre"] = ch["effet"].rank(method="first") | |
| 24 | + ch = ch.sort_values("effet") | |
| 25 | + col = {"en_vigueur": palette.cat[0], "remplace": palette.ink2, "projet": palette.cat[1], "reference": palette.cat[2]} | |
| 26 | + fin_axe = dt.date(2028, 6, 30) | |
| 27 | + labels = [] | |
| 28 | + for i, (_, r) in enumerate(ch.iterrows()): | |
| 29 | + c = col[r["statut"]] | |
| 30 | + fin = r["fin"] if r["fin"] is not None else (r["effet"] if r["statut"] == "projet" else fin_axe) | |
| 31 | + ls = ":" if r["statut"] == "projet" else "-" | |
| 32 | + if r["statut"] != "projet": | |
| 33 | + ax.plot([r["effet"], fin], [i, i], color=c, lw=2.2, solid_capstyle="butt", alpha=0.9 if r["statut"] != "remplace" else 0.6) | |
| 34 | + if r["publication"] is not None: | |
| 35 | + ax.plot([r["publication"], r["effet"]], [i, i], color=c, lw=1.0, ls=ls) | |
| 36 | + ax.plot(r["publication"], i, marker="o", mfc="white", mec=c, ms=5.5, mew=1.3) | |
| 37 | + ax.plot(r["effet"], i, marker="o", color=c, ms=5.5) | |
| 38 | + if r["statut"] == "projet": | |
| 39 | + ax.plot(r["effet"], i, marker="o", mfc="white", mec=c, ms=5.5, mew=1.3) | |
| 40 | + if r["fin"] is not None: | |
| 41 | + ax.plot(r["fin"], i, marker="|", color=c, ms=9, mew=1.6) | |
| 42 | + labels.append(r["cadre"] + (" *" if r.get("verification") == "partielle" else "")) | |
| 43 | + ax.axvline(mn.DATE_REF, color=palette.ink2, lw=0.8, ls="--") | |
| 44 | + ax.text(mn.DATE_REF, len(ch) - 0.4, " 31 oct. 2026", fontsize=7.5, color=palette.ink2, va="bottom") | |
| 45 | + ax.set_yticks(range(len(ch))); ax.set_yticklabels(labels, fontsize=8) | |
| 46 | + ax.tick_params(axis="y", length=0) | |
| 47 | + ax.set_ylim(-0.7, len(ch) - 0.3) | |
| 48 | + ax.set_xlim(dt.date(2012, 6, 1), fin_axe) | |
| 49 | + ax.xaxis.set_major_locator(mdates.YearLocator(2)); ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y")) | |
| 50 | + ax.grid(axis="y", visible=False) | |
| 51 | + for s in ("left",): ax.spines[s].set_visible(False) | |
| 52 | + h = [Line2D([0], [0], color=col["en_vigueur"], lw=2.2, label="en vigueur au 31 oct. 2026"), | |
| 53 | + Line2D([0], [0], color=col["remplace"], lw=2.2, alpha=0.6, label="remplacé"), | |
| 54 | + Line2D([0], [0], color=col["reference"], lw=2.2, label="norme de référence (IAAO)"), | |
| 55 | + Line2D([0], [0], color=col["projet"], lw=1.0, ls=":", marker="o", mfc="white", label="projet (date proposée)"), | |
| 56 | + Line2D([0], [0], color=palette.ink2, lw=0, marker="o", mfc="white", mec=palette.ink2, label="publication → entrée en vigueur")] | |
| 57 | + ax.legend(handles=h, loc="upper center", bbox_to_anchor=(0.5, -0.14), fontsize=7.5, ncol=3, frameon=False) | |
| 58 | + ax.set_xlabel("Année") | |
| 59 | + savefig(fig, "ch10/ch10-fig-chronologie-cadres.pdf") | |
| 60 | + | |
| 61 | +def fig_matrice(st): | |
| 62 | + fig, (a1, a2) = plt.subplots(1, 2, figsize=(LARGEUR, 2.9), gridspec_kw={"width_ratios": [1.15, 1]}) | |
| 63 | + fam = st["par_famille"].sort_values() | |
| 64 | + couleurs = [palette.cat[0] if f.startswith("OEAQ") else palette.ink2 for f in fam.index] | |
| 65 | + a1.barh(fam.index, fam.values, color=couleurs, height=0.62) | |
| 66 | + for y, v in enumerate(fam.values): | |
| 67 | + a1.text(v + 0.6, y, str(v), va="center", fontsize=8, color=palette.ink) | |
| 68 | + a1.set_xlabel("Dispositions (n = %d)" % st["n"]); a1.set_xlim(0, fam.max() * 1.18) | |
| 69 | + a1.set_title("Par famille de sources", loc="left", fontsize=9) | |
| 70 | + a1.grid(axis="y", visible=False) | |
| 71 | + top = st["par_chapitre"].head(10).sort_values() | |
| 72 | + couleurs2 = [palette.cat[0] if c == "ch10" else palette.ink2 for c in top.index] | |
| 73 | + a2.barh([c.replace("ch", "ch. ") for c in top.index], top.values, color=couleurs2, height=0.62) | |
| 74 | + for y, v in enumerate(top.values): | |
| 75 | + a2.text(v + 0.6, y, str(v), va="center", fontsize=8, color=palette.ink) | |
| 76 | + a2.set_xlabel("Renvois vers le chapitre"); a2.set_xlim(0, top.max() * 1.18) | |
| 77 | + a2.set_title("Dix chapitres les plus visés", loc="left", fontsize=9) | |
| 78 | + a2.grid(axis="y", visible=False) | |
| 79 | + fig.tight_layout(w_pad=2.0) | |
| 80 | + savefig(fig, "ch10/ch10-fig-matrice-normes.pdf") | |
| 81 | + | |
| 82 | +LIBELLES_COURTS = {1: "Identification physique et légale", 2: "Droits de propriété évalués", 3: "But et fin de l'évaluation", | |
| 83 | + 4: "Définition de la valeur", 5: "Date d'évaluation", 6: "Étendue du travail", 7: "Réserves et hypothèses", | |
| 84 | + 8: "Information et raisonnement", 9: "Usage le meilleur et le plus profitable", 10: "Rejet d'une méthode", | |
| 85 | + 11: "Conformité et écarts aux directives", 12: "Attestation"} | |
| 86 | + | |
| 87 | +def fig_grille(out): | |
| 88 | + extraits = list(cr.EXTRAITS.keys()) | |
| 89 | + fig, ax = plt.subplots(figsize=(LARGEUR, 3.4)) | |
| 90 | + for j, e in enumerate(cr.ELEMENTS): | |
| 91 | + for i, nom in enumerate(extraits): | |
| 92 | + strict = out["controle"][(nom, "strict")][e.cle][0] | |
| 93 | + souple = out["controle"][(nom, "souple")][e.cle][0] | |
| 94 | + if strict: | |
| 95 | + ax.scatter(i, j, s=120, marker="s", color=palette.cat[0], zorder=3) | |
| 96 | + elif souple: | |
| 97 | + ax.scatter(i, j, s=120, marker="s", facecolor="white", edgecolor=palette.cat[1], linewidth=1.6, zorder=3) | |
| 98 | + else: | |
| 99 | + ax.scatter(i, j, s=40, marker="x", color=palette.ink2, linewidth=1.0, zorder=3) | |
| 100 | + ax.set_xticks(range(len(extraits))) | |
| 101 | + ax.set_xticklabels([f"Extrait {n}\n{out['taux'][(n,'strict')]*12:.0f}/12 (strict)" for n in extraits], fontsize=8) | |
| 102 | + ax.set_yticks(range(len(cr.ELEMENTS))) | |
| 103 | + ax.set_yticklabels([f"{e.no}. {LIBELLES_COURTS[e.no]}" for e in cr.ELEMENTS], fontsize=7.5) | |
| 104 | + ax.invert_yaxis(); ax.set_xlim(-0.6, len(extraits) - 0.4) | |
| 105 | + ax.grid(False) | |
| 106 | + for s in ("left", "bottom"): ax.spines[s].set_visible(False) | |
| 107 | + ax.tick_params(length=0) | |
| 108 | + h = [Line2D([0], [0], marker="s", color=palette.cat[0], lw=0, ms=8, label="détecté (lexique strict)"), | |
| 109 | + Line2D([0], [0], marker="s", mfc="white", mec=palette.cat[1], mew=1.6, lw=0, ms=8, label="détecté seulement par le lexique souple"), | |
| 110 | + Line2D([0], [0], marker="x", color=palette.ink2, lw=0, ms=6, label="non détecté")] | |
| 111 | + ax.legend(handles=h, loc="upper center", bbox_to_anchor=(0.5, -0.16), ncol=3, fontsize=7.5, frameon=False) | |
| 112 | + savefig(fig, "ch10/ch10-fig-grille-controle.pdf") | |
| 113 | + | |
| 114 | +def main(): | |
| 115 | + setup() | |
| 116 | + res = mn.main(verbose=False) | |
| 117 | + out = cr.main(verbose=False) | |
| 118 | + fig_chronologie(res["chronologie"]) | |
| 119 | + fig_matrice(res["stats"]) | |
| 120 | + fig_grille(out) | |
| 121 | + print("figures du chapitre 10 écrites dans assets/figures/ch10/") | |
| 122 | + | |
| 123 | +if __name__ == "__main__": | |
| 124 | + main() | |
added
analysis/python/tests/test_ch01.py
+169 −0
@@ -0,0 +1,169 @@ | ||
| 1 | +"""Chapitre 1 — vérification des identités et des chiffres cités dans le texte et les solutions (données synthétiques, cas 1).""" | |
| 2 | +import sys, pathlib, math | |
| 3 | +import numpy as np, pytest | |
| 4 | +from scipy.stats import norm | |
| 5 | +from scipy.optimize import brentq | |
| 6 | +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases")) | |
| 7 | +import ch01_valeur_prix as c1 | |
| 8 | + | |
| 9 | +@pytest.fixture(scope="module") | |
| 10 | +def t(): | |
| 11 | + return c1.tout() | |
| 12 | + | |
| 13 | +# ---------------------------------------------------------------------------- modèle et dispersion | |
| 14 | + | |
| 15 | +def test_modele_et_dispersion(t): | |
| 16 | + m, d = t["modele"], t["dispersion"] | |
| 17 | + assert m["n"] == 600 and m["k"] == 16 | |
| 18 | + assert m["sigma"] == pytest.approx(0.0830, abs=0.0005) | |
| 19 | + assert d["ecart_type_residus"] == pytest.approx(0.0820, abs=0.0005) | |
| 20 | + assert m["r2"] == pytest.approx(0.853, abs=0.001) and m["r2_ajuste"] == pytest.approx(0.849, abs=0.001) | |
| 21 | + assert d["part_dans_pm10pct"] == pytest.approx(0.745, abs=0.002) and d["part_dans_pm5pct"] == pytest.approx(0.448, abs=0.002) | |
| 22 | + assert abs(d["asymetrie"]) < 0.15 and abs(d["aplatissement_exces"]) < 0.5 | |
| 23 | + assert abs(float(np.mean(m["residus"]))) < 1e-8 # MCO avec constante : résidus centrés | |
| 24 | + | |
| 25 | +def test_esperance_conditionnelle_sujet(t): | |
| 26 | + p = t["pred"]; sigma = p["sigma"] | |
| 27 | + assert p["mediane"] == pytest.approx(635821, abs=1) | |
| 28 | + assert p["mediane"] == pytest.approx(math.exp(p["ln_pred"]), rel=1e-12) | |
| 29 | + assert p["moyenne_lognormale"] == pytest.approx(p["mediane"] * math.exp(sigma ** 2 / 2), rel=1e-12) | |
| 30 | + assert p["mode_lognormal"] == pytest.approx(p["mediane"] * math.exp(-sigma ** 2), rel=1e-12) | |
| 31 | + assert p["facteur_duan"] == pytest.approx(1.00335, abs=0.00005) | |
| 32 | + assert p["facteur_duan"] == pytest.approx(math.exp(sigma ** 2 / 2), abs=0.001) # Duan ≈ correction log-normale | |
| 33 | + assert p["moyenne_duan"] == pytest.approx(637953, abs=1) and p["mode_lognormal"] == pytest.approx(631455, abs=1) | |
| 34 | + assert p["ip_bas"] == pytest.approx(539682, abs=1) and p["ip_haut"] == pytest.approx(749087, abs=1) | |
| 35 | + assert p["ip_bas"] < p["mode_lognormal"] < p["mediane"] < p["moyenne_lognormale"] < p["ip_haut"] | |
| 36 | + # l'intervalle est symétrique en log autour de la prédiction | |
| 37 | + assert math.log(p["ip_haut"]) - p["ln_pred"] == pytest.approx(p["ln_pred"] - math.log(p["ip_bas"]), rel=1e-9) | |
| 38 | + | |
| 39 | +def test_probabilites_ecart(t): | |
| 40 | + sigma = t["modele"]["sigma"] | |
| 41 | + assert c1.proba_ecart(sigma, 0.10) == pytest.approx(0.251, abs=0.002) | |
| 42 | + assert c1.proba_ecart(sigma, 0.05) == pytest.approx(0.557, abs=0.002) | |
| 43 | + assert c1.proba_ecart(sigma, 0.20) == pytest.approx(0.0281, abs=0.0005) | |
| 44 | + assert c1.proba_ecart(sigma, 0.0) == pytest.approx(1.0) | |
| 45 | + assert c1.proba_ecart(0.04, 0.10) < c1.proba_ecart(0.083, 0.10) < c1.proba_ecart(0.16, 0.10) | |
| 46 | + | |
| 47 | +# ---------------------------------------------------------------------------- principes | |
| 48 | + | |
| 49 | +def test_contributions(t): | |
| 50 | + c = t["contrib"]; p = t["pred"] | |
| 51 | + assert c.loc["garage", "contribution_dollars"] == pytest.approx(20291, abs=1) | |
| 52 | + assert c.loc["sous_sol", "contribution_dollars"] == pytest.approx(13829, abs=1) | |
| 53 | + assert c.loc["etages", "contribution_dollars"] == pytest.approx(18860, abs=1) | |
| 54 | + assert c.loc["ln_S", "contribution_dollars"] == pytest.approx(53893, abs=1) | |
| 55 | + assert c.loc["age", "contribution_dollars"] + c.loc["age2_100", "contribution_dollars"] == pytest.approx(-84497, abs=2) | |
| 56 | + assert c.loc["mois", "contribution_dollars"] == pytest.approx(57385, abs=1) | |
| 57 | + # identité : constante + somme des β_k x_k = ln V̂ | |
| 58 | + const = float(t["modele"]["beta"][0]) | |
| 59 | + assert const + c["beta_x"].sum() == pytest.approx(p["ln_pred"], abs=1e-9) | |
| 60 | + # contribution par retrait : P̂ [1 − exp(−βx)] | |
| 61 | + assert c.loc["garage", "contribution_dollars"] == pytest.approx(p["mediane"] * (1 - math.exp(-c.loc["garage", "beta_x"])), rel=1e-12) | |
| 62 | + assert c.loc["garage", "effet_pct_unitaire"] == pytest.approx(0.0330, abs=0.0002) | |
| 63 | + assert c.loc["piscine", "effet_pct_unitaire"] == pytest.approx(0.0267, abs=0.0002) | |
| 64 | + assert c.loc["qualite", "effet_pct_unitaire"] == pytest.approx(0.0630, abs=0.0002) | |
| 65 | + | |
| 66 | +def test_rendements_decroissants(t): | |
| 67 | + r = t["rendements"]; p = t["pred"] | |
| 68 | + assert r["elasticite_S"] == pytest.approx(0.5391, abs=0.0005) | |
| 69 | + assert r["prix_moyen_m2"] == pytest.approx(3853, abs=1) and r["prix_marginal_m2"] == pytest.approx(2077, abs=1) | |
| 70 | + assert r["prix_marginal_m2"] == pytest.approx(r["elasticite_S"] * r["prix_moyen_m2"], rel=1e-12) | |
| 71 | + assert r["gain_10m2"] == pytest.approx(20491, abs=1) and r["gain_10m2_suivants"] == pytest.approx(19958, abs=1) | |
| 72 | + assert r["gain_10m2_suivants"] < r["gain_10m2"] | |
| 73 | + assert r["gain_10m2"] + r["gain_10m2_suivants"] == pytest.approx(p["mediane"] * ((185 / 165) ** r["elasticite_S"] - 1), rel=1e-9) | |
| 74 | + | |
| 75 | +def test_anticipation(t): | |
| 76 | + a = t["anticipation"] | |
| 77 | + assert a["croissance_mensuelle"] == pytest.approx(0.003642, abs=0.00001) | |
| 78 | + assert a["croissance_annuelle"] == pytest.approx(0.0446, abs=0.0002) | |
| 79 | + assert a["croissance_annuelle"] == pytest.approx((1 + a["croissance_mensuelle"]) ** 12 - 1, rel=1e-12) | |
| 80 | + assert a["mois_depuis_reference"] == pytest.approx(26.0, abs=0.1) | |
| 81 | + assert a["facteur_cumule"] == pytest.approx(1.0992, abs=0.0002) | |
| 82 | + assert a["valeur_a_la_reference"] == pytest.approx(578436, abs=1) | |
| 83 | + assert a["valeur_role"] == 468000 | |
| 84 | + | |
| 85 | +def test_conformite(t): | |
| 86 | + c = t["conformite"] | |
| 87 | + assert c.loc["Aylmer", "effet_pct"] == pytest.approx(0.0892, abs=0.0002) and c.loc["Aylmer", "valeur"] == pytest.approx(692518, abs=1) | |
| 88 | + assert c.loc["Masson-Angers", "effet_pct"] == pytest.approx(-0.2314, abs=0.0002) and c.loc["Masson-Angers", "valeur"] == pytest.approx(488712, abs=1) | |
| 89 | + assert c.loc["Buckingham", "valeur"] == pytest.approx(494999, abs=1) and c.loc["Gatineau", "valeur"] == pytest.approx(621798, abs=1) | |
| 90 | + assert c.loc["Hull", "valeur"] == pytest.approx(t["pred"]["mediane"]) | |
| 91 | + | |
| 92 | +def test_comparables_ch16(t): | |
| 93 | + c = t["comparables"].set_index("id_vente") | |
| 94 | + assert list(c.index) == ["G0574", "G0404", "G0468", "G0477", "G0575"] | |
| 95 | + assert c.loc["G0574", "ecart_pct"] == pytest.approx(0.007, abs=0.001) | |
| 96 | + assert c.loc["G0404", "ecart_pct"] == pytest.approx(-0.149, abs=0.001) | |
| 97 | + assert c.loc["G0468", "ecart_pct"] == pytest.approx(-0.099, abs=0.001) | |
| 98 | + assert c.loc["G0477", "ecart_pct"] == pytest.approx(-0.105, abs=0.001) | |
| 99 | + assert c.loc["G0575", "ecart_pct"] == pytest.approx(-0.080, abs=0.001) | |
| 100 | + assert c.loc["G0404", "valeur_estimee"] == pytest.approx(595030, abs=1) | |
| 101 | + assert (c["ecart_pct"] == pytest.approx(np.exp(c["residu_log"].values) - 1, rel=1e-9)) if "residu_log" in c else True | |
| 102 | + | |
| 103 | +# ---------------------------------------------------------------------------- recherche | |
| 104 | + | |
| 105 | +def test_recherche(t): | |
| 106 | + s = t["recherche"]; m = t["pred"]["mediane"]; P = c1.PARAMS_RECHERCHE | |
| 107 | + assert s["prix_reserve"] / m == pytest.approx(1.0267, abs=0.0005) | |
| 108 | + assert s["prix_moyen"] / m == pytest.approx(1.068, abs=0.003) | |
| 109 | + assert s["cv_prix"] == pytest.approx(0.0317, abs=0.001) | |
| 110 | + assert s["delai_moyen"] == pytest.approx(5.08, abs=0.1) and s["delai_theorique"] == pytest.approx(5.05, abs=0.02) | |
| 111 | + assert s["delai_moyen"] == pytest.approx(s["delai_theorique"], rel=0.03) | |
| 112 | + assert s["prix"].min() >= s["prix_reserve"] # aucun prix accepté sous le prix de réserve | |
| 113 | + # point fixe : W = −c + δ [p E max(O, W) + (1 − p) W] | |
| 114 | + r = s["prix_reserve"]; delta = 1 / (1 + P["taux_annuel"] / 12); c = P["c_mois"] * m | |
| 115 | + z = np.linspace(-6, 6, 20001); w = norm.pdf(z); w /= w.sum(); O = m * np.exp(P["sigma_o"] * z) | |
| 116 | + EmaxO = float(np.sum(w * np.maximum(O, r))) | |
| 117 | + assert r == pytest.approx(-c + delta * (P["p_offre"] * EmaxO + (1 - P["p_offre"]) * r), rel=1e-4) | |
| 118 | + tab = t["recherche_tableau"] | |
| 119 | + assert tab.loc[0.002, "prix_reserve_sur_V"] == pytest.approx(1.0377, abs=0.0005) and tab.loc[0.002, "delai_moyen_mois"] == pytest.approx(6.22, abs=0.15) | |
| 120 | + assert tab.loc[0.015, "prix_reserve_sur_V"] == pytest.approx(0.9869, abs=0.0005) and tab.loc[0.015, "delai_moyen_mois"] == pytest.approx(2.88, abs=0.1) | |
| 121 | + assert tab["prix_reserve_sur_V"].is_monotonic_decreasing and tab["delai_moyen_mois"].is_monotonic_decreasing and tab["prix_moyen_sur_V"].is_monotonic_decreasing | |
| 122 | + | |
| 123 | +def test_phmu(t): | |
| 124 | + p = t["phmu"] | |
| 125 | + assert list(p["valeur_residuelle_terrain"]) == [170_000, 160_000, 140_000] | |
| 126 | + assert p["valeur_residuelle_terrain"].idxmax() == "Unifamiliale (usage actuel)" | |
| 127 | + | |
| 128 | +def test_sensibilite_sigma(t): | |
| 129 | + s = t["sensibilite"] | |
| 130 | + assert s.loc[0.04, "ip95_bas_pct"] == pytest.approx(-0.0754, abs=0.0003) and s.loc[0.04, "ip95_haut_pct"] == pytest.approx(0.0816, abs=0.0003) | |
| 131 | + assert s.loc[0.083, "ip95_bas_pct"] == pytest.approx(-0.150, abs=0.001) and s.loc[0.083, "ip95_haut_pct"] == pytest.approx(0.177, abs=0.001) | |
| 132 | + assert s.loc[0.16, "ip95_haut_pct"] == pytest.approx(0.368, abs=0.001) and s.loc[0.16, "proba_ecart_10pct"] == pytest.approx(0.551, abs=0.002) | |
| 133 | + assert s.loc[0.04, "proba_ecart_10pct"] == pytest.approx(0.0172, abs=0.0005) | |
| 134 | + assert s.loc[0.083, "moyenne_sur_mediane"] == pytest.approx(0.0035, abs=0.0001) and s.loc[0.16, "moyenne_sur_mediane"] == pytest.approx(0.0129, abs=0.0001) | |
| 135 | + assert list(s["n_pour_se_2pct"]) == [16, 36, 68, 142, 251] | |
| 136 | + | |
| 137 | +# ---------------------------------------------------------------------------- solutions des exercices | |
| 138 | + | |
| 139 | +def test_solutions_exercices(): | |
| 140 | + P = 635821.26 | |
| 141 | + # ex. 1.2 : contribution d'un ajout = P [exp(β) − 1] | |
| 142 | + assert P * (math.exp(0.02633) - 1) == pytest.approx(16964, abs=2) | |
| 143 | + assert P * (math.exp(0.03243) - 1) == pytest.approx(20958, abs=2) | |
| 144 | + # ex. 1.3 : prix moyen, marginal, agrandissement de 25 m² | |
| 145 | + b = 0.53907 | |
| 146 | + assert P / 165 == pytest.approx(3853, abs=1) and b * P / 165 == pytest.approx(2077, abs=1) | |
| 147 | + assert P * ((190 / 165) ** b - 1) == pytest.approx(50241, abs=2) | |
| 148 | + assert P * ((190 / 165) ** b - 1) / 25 == pytest.approx(2010, abs=1) | |
| 149 | + # ex. 1.4 : log-normale σ = 0,12, médiane 500 000 | |
| 150 | + s, med = 0.12, 500_000 | |
| 151 | + assert med * math.exp(s ** 2 / 2) == pytest.approx(503613, abs=1) and med * math.exp(-s ** 2) == pytest.approx(492852, abs=1) | |
| 152 | + assert med * math.exp(-1.96 * s) == pytest.approx(395206, abs=1) and med * math.exp(1.96 * s) == pytest.approx(632581, abs=1) | |
| 153 | + assert c1.proba_ecart(s, 0.10) == pytest.approx(0.427, abs=0.001) | |
| 154 | + # ex. 1.5 : Gordon et coût d'usage | |
| 155 | + assert 24_000 / (0.06 - 0.02) == pytest.approx(600_000) | |
| 156 | + assert 600_000 * (0.045 + 0.01 + 0.015 - 0.02) == pytest.approx(30_000) and 600_000 * (0.06 + 0.01 + 0.015 - 0.02) == pytest.approx(39_000) | |
| 157 | + # ex. 1.6 : prix de réserve avec offres uniformes sur [0,9 ; 1,1], c = 0,005, δ = 1 | |
| 158 | + a, bb = 0.9, 1.1 | |
| 159 | + def fixe(c): | |
| 160 | + return brentq(lambda r: -c + r * (r - a) / (bb - a) + (bb ** 2 - r ** 2) / (2 * (bb - a)) - r, a, bb) | |
| 161 | + r1 = fixe(0.005); assert r1 == pytest.approx(1.0553, abs=0.0001) | |
| 162 | + assert (bb - a) / (bb - r1) == pytest.approx(4.47, abs=0.01) and (r1 + bb) / 2 == pytest.approx(1.0776, abs=0.0001) | |
| 163 | + r2 = fixe(0.02); assert r2 == pytest.approx(1.0106, abs=0.0001) and (bb - a) / (bb - r2) == pytest.approx(2.24, abs=0.01) | |
| 164 | + assert r1 == pytest.approx(1.1 - math.sqrt(0.4 * 0.005), abs=1e-9) # forme fermée : r = b − sqrt(2c(b−a)) | |
| 165 | + # ex. 1.7 : ancrage, λ par MCO sans constante | |
| 166 | + x = np.array([40, -30, 25, 60, -20, 15.0]); y = np.array([18, -12, 9, 27, -10, 6.0]) | |
| 167 | + lam = float(x @ y / (x @ x)); assert lam == pytest.approx(0.437, abs=0.001) | |
| 168 | + assert lam * 50 == pytest.approx(21.9, abs=0.05) | |
| 169 | + assert c1.estimation_ancree(600_000, 650_000, lam) == pytest.approx(600_000 + lam * 50_000) | |
added
analysis/python/tests/test_ch10.py
+94 −0
@@ -0,0 +1,94 @@ | ||
| 1 | +"""Chapitre 10 — vérification des chiffres cités dans le texte, les tableaux et les solutions.""" | |
| 2 | +import sys, pathlib, datetime as dt | |
| 3 | +import pytest | |
| 4 | +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases")) | |
| 5 | +import ch10_controle_rapport as cr | |
| 6 | +import ch10_matrice_normes as mn | |
| 7 | + | |
| 8 | +@pytest.fixture(scope="module") | |
| 9 | +def out(): | |
| 10 | + return cr.main(verbose=False) | |
| 11 | + | |
| 12 | +@pytest.fixture(scope="module") | |
| 13 | +def mat(): | |
| 14 | + return mn.main(verbose=False) | |
| 15 | + | |
| 16 | +# --- contrôle lexical des douze éléments (tableau 10.7, figure grille) ------------------------------------------- | |
| 17 | +def test_douze_elements_definis(): | |
| 18 | + assert [e.no for e in cr.ELEMENTS] == list(range(1, 13)) | |
| 19 | + assert len({e.cle for e in cr.ELEMENTS}) == 12 | |
| 20 | + | |
| 21 | +def test_extrait_complet_et_lacunaires(out): | |
| 22 | + assert out["manquants"][("D", "strict")] == [] and out["taux"][("D", "strict")] == 1.0 | |
| 23 | + assert out["manquants"][("A", "strict")] == [1, 2, 4, 5, 6, 7, 9, 10, 11, 12] | |
| 24 | + assert out["manquants"][("A", "souple")] == [2, 4, 5, 7, 9, 10, 11] | |
| 25 | + assert out["manquants"][("B", "strict")] == [6, 10] | |
| 26 | + assert out["manquants"][("C", "strict")] == [2, 3, 4, 9, 11, 12] | |
| 27 | + assert out["taux"][("A", "strict")] == pytest.approx(2 / 12) | |
| 28 | + assert out["taux"][("B", "strict")] == pytest.approx(10 / 12) | |
| 29 | + assert out["taux"][("C", "strict")] == pytest.approx(0.5) | |
| 30 | + | |
| 31 | +def test_lexique_souple_jamais_plus_severe(out): | |
| 32 | + for nom in cr.EXTRAITS: | |
| 33 | + s, l = out["manquants"][(nom, "strict")], out["manquants"][(nom, "souple")] | |
| 34 | + assert set(l) <= set(s) | |
| 35 | + | |
| 36 | +def test_taux_conformite_identite(): | |
| 37 | + res = {e.cle: (e.no % 2 == 0, None) for e in cr.ELEMENTS} | |
| 38 | + assert cr.taux_conformite(res) == pytest.approx(0.5) | |
| 39 | + assert cr.manquants(res) == [1, 3, 5, 7, 9, 11] | |
| 40 | + | |
| 41 | +def test_controle_insensible_aux_accents_et_casse(): | |
| 42 | + r1 = cr.controler("DATE D'ÉVALUATION : 1er septembre 2026. J'ATTESTE par la présente.") | |
| 43 | + r2 = cr.controler("date d'evaluation : 1er septembre 2026. j'atteste par la presente.") | |
| 44 | + assert r1["date"][0] and r1["attestation"][0] and r1 == r2 | |
| 45 | + | |
| 46 | +# --- probabilité de détection par révisions indépendantes (tableau 10.8) --------------------------------------- | |
| 47 | +def test_p_detection(out): | |
| 48 | + assert cr.p_detection(0.5, 1) == 0.5 and cr.p_detection(0.5, 2) == 0.75 and cr.p_detection(0.5, 3) == 0.875 | |
| 49 | + assert cr.p_detection(0.7, 2) == pytest.approx(0.91) and cr.p_detection(0.7, 3) == pytest.approx(0.973) | |
| 50 | + assert cr.p_detection(0.9, 2) == pytest.approx(0.99) and cr.p_detection(0.9, 3) == pytest.approx(0.999) | |
| 51 | + assert cr.p_detection(0.0, 5) == 0.0 and cr.p_detection(1.0, 1) == 1.0 | |
| 52 | + vals = [cr.p_detection(0.7, n) for n in range(1, 6)] | |
| 53 | + assert all(b > a for a, b in zip(vals, vals[1:])) | |
| 54 | + | |
| 55 | +# --- hypothèse extraordinaire : incidence et espérance (section 10.2, exercice 10.5) --------------------------- | |
| 56 | +def test_incidence_hypothese(out): | |
| 57 | + dv, dr = out["incidence_B"] | |
| 58 | + assert dv == 37000 and dr == pytest.approx(0.0643, abs=0.0001) | |
| 59 | + assert out["esperance_B"] == pytest.approx(597200) | |
| 60 | + assert cr.esperance_sous_incertitude(1.0, 612000, 575000) == 612000 | |
| 61 | + assert cr.esperance_sous_incertitude(0.0, 612000, 575000) == 575000 | |
| 62 | + | |
| 63 | +# --- échéances réglementaires (exercice 10.2) ----------------------------------------------------------------- | |
| 64 | +def test_conservation_et_formation(out): | |
| 65 | + assert out["conservation"] == dt.date(2031, 11, 15) | |
| 66 | + f = out["formation"] | |
| 67 | + assert f["deficit_total"] == 0 and f["deficit_ethique"] == 1 and f["report_suivant"] == 1 and not f["conforme"] | |
| 68 | + g = out["formation_b"] | |
| 69 | + assert g["conforme"] and g["report_suivant"] == 3 | |
| 70 | + h = cr.bilan_formation(heures_total=20, heures_ethique=3, report_anterieur=8) # report plafonné à 5 h | |
| 71 | + assert h["deficit_total"] == 5 and not h["conforme"] | |
| 72 | + | |
| 73 | +# --- statistiques de la matrice des dispositions (tableau 10.9, figure matrice) --------------------------------- | |
| 74 | +def test_matrice_normes(mat): | |
| 75 | + st = mat["stats"] | |
| 76 | + assert st["n"] == 100 and st["n_domaines"] == 17 and st["n_chapitres_couverts"] == 28 | |
| 77 | + assert st["par_domaine"]["OEAQ-NPP"] == 15 and st["par_domaine"]["OEAQ-Déontologie"] == 14 and st["par_domaine"]["OEAQ-Règlements"] == 12 | |
| 78 | + assert st["par_domaine"]["IVS"] == 11 and st["par_domaine"]["CUSPAP"] == 8 and st["par_domaine"]["IAAO"] == 6 | |
| 79 | + assert st["par_famille"]["Cadres hors Québec"] == 27 and st["par_famille"]["Finance et prudentiel"] == 20 | |
| 80 | + assert st["n_oeaq"] == 43 and st["n_ch10"] == 51 | |
| 81 | + assert st["par_chapitre"]["ch10"] == 51 and st["par_chapitre"]["ch31"] == 15 and st["par_chapitre"]["ch20"] == 14 | |
| 82 | + assert st["moy_chapitres_par_disposition"] == pytest.approx(1.70) and st["max_chapitres_par_disposition"] == 4 | |
| 83 | + assert st["part_multi_chapitres"] == pytest.approx(0.51) | |
| 84 | + assert st["par_famille"].sum() == 100 | |
| 85 | + | |
| 86 | +def test_chronologie(mat): | |
| 87 | + ch = mat["chronologie"].set_index("cadre") | |
| 88 | + assert ch.loc["CUSPAP 2024 (Canada)", "en_vigueur_2026_10_31"] == False | |
| 89 | + assert ch.loc["IVS 2028 — projet (exposure draft)", "en_vigueur_2026_10_31"] == False | |
| 90 | + assert ch["en_vigueur_2026_10_31"].sum() == 8 | |
| 91 | + assert ch.loc["IVS 2025 (IVSC)", "delai_publication_effet_jours"] == 366 | |
| 92 | + assert ch.loc["RICS Red Book Global (2025)", "delai_publication_effet_jours"] == 51 | |
| 93 | + assert ch.loc["NPP de l'OEAQ (Québec)", "effet"] == dt.date(2024, 2, 1) | |
| 94 | + assert ch.loc["CUSPAP 2026 (Canada)", "effet"] == dt.date(2026, 4, 1) | |
added
assets/figures/ch01/ch01-fig-prix-valeur.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch01/ch01-fig-recherche.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch01/ch01-fig-residus.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch01/ch01-fig-rosen.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch01/ch01-fig-sensibilite-sigma.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch02/ch02-fig-decomposition-droits.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch02/ch02-fig-ecart-logements.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch06/ch06-fig-emphyteose.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch06/ch06-fig-usufruit.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch07/ch07-fig-fonds-prevoyance.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch07/ch07-fig-paires-servitude.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch07/ch07-fig-repartition-charges.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch07/ch07-fig-sensibilite-cotisation.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch08/ch08-fig-carte-zones.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch08/ch08-fig-option-sensibilite.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch08/ch08-fig-rezonage-paires.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch10/ch10-fig-chronologie-cadres.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch10/ch10-fig-grille-controle.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch10/ch10-fig-matrice-normes.pdf
+0 −0
Binary file not shown.
added
data/raw/gatineau_r994_2025/README.md
+7 −0
@@ -0,0 +1,7 @@ | ||
| 1 | +# Règlement 994-2025 de la Ville de Gatineau — taux de taxes 2026 (extraction du 2026-10-31) | |
| 2 | + | |
| 3 | +- **Source** : Ville de Gatineau, *Règlement numéro 994-2025 décrétant l'imposition et le prélèvement des taxes foncières générales, spéciales et des compensations pour le budget de l'année 2026*, adopté le 18 décembre 2025 (avis de motion AM-2025-790 du 16 décembre 2025). PDF de 21 pages téléchargé le 2026-10-31 : https://docweb.gatineau.ca/Doc-Web/masson/documents/pdf/R%20994-2025.pdf (clé biblatex `gatineaureglement994`, source SRC-E-028 / SRC-09-001). | |
| 4 | +- **Statut** : document public municipal (droit d'auteur de la Ville de Gatineau) ; les taux sont des faits réglementaires cités avec attribution. Le PDF lui-même n'est pas redistribué dans le dépôt. | |
| 5 | +- **Fichier** : `annexe1_taux_voisinage.csv` — les 1 092 paires (unité de voisinage, taux 2026 par 1 000 $ d'évaluation) de l'annexe I (« Taxe générale résidentielle — Taux des unités de voisinage »), extraites par `pdftotext -layout` puis expression régulière `([A-Z]{1,2}\d{2,3})\s+(\d,\d{3})` ; décimale convertie en point. Un code d'unité apparaît deux fois dans l'annexe (1 091 codes distincts pour 1 092 lignes) ; les deux lignes sont conservées telles quelles. | |
| 6 | +- **Autres taux lus dans le règlement (par 1 000 $ d'évaluation)** : taux de base 5,224 (art. 1) ; non résidentiel 29,372 (art. 2) ; industriel 26,120 (art. 3) ; terrains vagues desservis 20,896 (art. 4) ; agricole 5,090 et forestier 5,090 (art. 5-6) ; résidentiel (catégorie résiduelle autre que six logements ou plus) : taux de l'unité de voisinage, annexe I (art. 7) ; sous-catégorie des six logements ou plus 6,964 (art. 8) ; fonds dédié aux infrastructures 0,579 inclus dans les taux (art. 10) ; taxe spéciale transport en commun (STO) 1,417 (art. 11) ; taxe spéciale service de la dette 0,830 (art. 12) ; crédits aqueduc 0,386 et égout 0,523 pour les immeubles non raccordés (art. 13-14) ; taxe spéciale ex-Ville de Hull 0,036 (art. 15, 1°) ; compensation matières résiduelles 178,83 $ par logement et 59,61 $ par chambre locative (art. 16) ; eau au compteur 0,983 $/m³ au-delà de 300 m³ (art. 18) ; piscine 80 $ (art. 19). | |
| 7 | +- **Statistiques calculées** (`analysis/python/cases/ch09_ratios.py`) : minimum 1,613 ; maximum 7,544 ; médiane 5,232 ; moyenne 5,377. | |
added
data/raw/gatineau_r994_2025/annexe1_taux_voisinage.csv
+1093 −0
@@ -0,0 +1,1093 @@ | ||
| 1 | +unite_voisinage,taux_2026_par_1000 | |
| 2 | +A701,5.650 | |
| 3 | +AJ11,4.722 | |
| 4 | +AL11,5.571 | |
| 5 | +A800,4.061 | |
| 6 | +AJ12,5.356 | |
| 7 | +AL13,5.020 | |
| 8 | +A999,7.544 | |
| 9 | +AJ13,4.998 | |
| 10 | +AL14,4.893 | |
| 11 | +AA05,5.582 | |
| 12 | +AJ18,5.030 | |
| 13 | +AL15,4.713 | |
| 14 | +AA15,5.833 | |
| 15 | +AJ19,5.101 | |
| 16 | +AL18,4.722 | |
| 17 | +AC02,5.644 | |
| 18 | +AJ20,5.037 | |
| 19 | +AL21,4.962 | |
| 20 | +AC04,5.920 | |
| 21 | +AJ21,4.987 | |
| 22 | +AL22,5.549 | |
| 23 | +AC07,4.547 | |
| 24 | +AJ22,5.008 | |
| 25 | +AL23,5.096 | |
| 26 | +AC11,5.259 | |
| 27 | +AJ23,5.720 | |
| 28 | +AL25,5.688 | |
| 29 | +AC12,4.919 | |
| 30 | +AJ24,5.041 | |
| 31 | +AL26,5.407 | |
| 32 | +AC18,4.061 | |
| 33 | +AJ27,4.965 | |
| 34 | +AL27,5.444 | |
| 35 | +AC19,4.061 | |
| 36 | +AJ28,4.886 | |
| 37 | +AL28,4.061 | |
| 38 | +AD08,4.730 | |
| 39 | +AJ29,4.897 | |
| 40 | +AL29,5.404 | |
| 41 | +AD09,5.594 | |
| 42 | +AJ30,5.085 | |
| 43 | +AL30,5.265 | |
| 44 | +AD10,4.061 | |
| 45 | +AJ32,4.682 | |
| 46 | +AL32,5.530 | |
| 47 | +AD11,7.544 | |
| 48 | +AJ34,5.102 | |
| 49 | +AL33,5.412 | |
| 50 | +AD12,7.544 | |
| 51 | +AJ35,5.750 | |
| 52 | +AL34,4.934 | |
| 53 | +AD19,4.742 | |
| 54 | +AJ36,4.988 | |
| 55 | +AL35,5.398 | |
| 56 | +AD35,4.061 | |
| 57 | +AJ37,5.062 | |
| 58 | +AL37,5.925 | |
| 59 | +AD56,7.175 | |
| 60 | +AJ39,5.070 | |
| 61 | +AL40,5.133 | |
| 62 | +AD57,6.057 | |
| 63 | +AJ40,4.891 | |
| 64 | +AL41,4.814 | |
| 65 | +AD60,7.360 | |
| 66 | +AJ41,4.891 | |
| 67 | +AL42,5.463 | |
| 68 | +AG06,6.646 | |
| 69 | +AJ42,5.268 | |
| 70 | +AL43,6.677 | |
| 71 | +AI70,4.061 | |
| 72 | +AJ43,5.212 | |
| 73 | +AL44,5.575 | |
| 74 | +AI71,4.061 | |
| 75 | +AJ44,4.803 | |
| 76 | +AL45,5.390 | |
| 77 | +AI72,5.213 | |
| 78 | +AJ45,5.166 | |
| 79 | +AL47,5.299 | |
| 80 | +AJ00,4.624 | |
| 81 | +AJ46,4.983 | |
| 82 | +AL51,5.278 | |
| 83 | +AJ01,4.822 | |
| 84 | +AJ47,5.170 | |
| 85 | +AM01,4.923 | |
| 86 | +AJ02,5.468 | |
| 87 | +AJ48,5.672 | |
| 88 | +AM02,5.311 | |
| 89 | +AJ03,4.837 | |
| 90 | +AL02,5.276 | |
| 91 | +AM04,5.818 | |
| 92 | +AJ04,4.780 | |
| 93 | +AL03,4.599 | |
| 94 | +AM05,5.369 | |
| 95 | +AJ05,4.547 | |
| 96 | +AL04,4.476 | |
| 97 | +AM06,4.950 | |
| 98 | +AJ06,5.490 | |
| 99 | +AL07,6.366 | |
| 100 | +AM08,5.286 | |
| 101 | +AJ08,4.832 | |
| 102 | +AL08,5.749 | |
| 103 | +AM11,5.581 | |
| 104 | +AJ09,4.744 | |
| 105 | +AL09,5.534 | |
| 106 | +AM13,5.406 | |
| 107 | +AJ10,5.196 | |
| 108 | +AL10,5.152 | |
| 109 | +AM15,4.420 | |
| 110 | +AM16,5.981 | |
| 111 | +AO42,5.483 | |
| 112 | +AR75,5.428 | |
| 113 | +AM20,4.987 | |
| 114 | +AO43,5.054 | |
| 115 | +AR76,5.769 | |
| 116 | +AM21,5.876 | |
| 117 | +AO44,5.431 | |
| 118 | +AR77,4.621 | |
| 119 | +AM23,4.682 | |
| 120 | +AO45,7.049 | |
| 121 | +AR78,5.114 | |
| 122 | +AM24,5.624 | |
| 123 | +AO46,5.597 | |
| 124 | +AR79,4.960 | |
| 125 | +AM25,4.971 | |
| 126 | +AO47,5.320 | |
| 127 | +AR80,4.897 | |
| 128 | +AM26,5.274 | |
| 129 | +AO48,4.988 | |
| 130 | +AR81,4.699 | |
| 131 | +AM27,5.229 | |
| 132 | +AO49,7.264 | |
| 133 | +AR82,5.098 | |
| 134 | +AO02,4.858 | |
| 135 | +AO50,5.404 | |
| 136 | +AR83,5.221 | |
| 137 | +AO03,4.126 | |
| 138 | +AO51,6.772 | |
| 139 | +AR84,4.701 | |
| 140 | +AO04,5.500 | |
| 141 | +AO52,6.379 | |
| 142 | +AR85,5.167 | |
| 143 | +AO05,5.199 | |
| 144 | +AO53,5.298 | |
| 145 | +AR86,5.301 | |
| 146 | +AO06,5.847 | |
| 147 | +AP16,6.472 | |
| 148 | +AR87,4.640 | |
| 149 | +AO07,6.020 | |
| 150 | +AP20,5.624 | |
| 151 | +AR88,5.548 | |
| 152 | +AO09,4.761 | |
| 153 | +AP21,6.427 | |
| 154 | +AR89,5.151 | |
| 155 | +AO10,5.573 | |
| 156 | +AP22,4.907 | |
| 157 | +AU01,5.913 | |
| 158 | +AO11,4.341 | |
| 159 | +AP42,4.061 | |
| 160 | +AU02,4.713 | |
| 161 | +AO12,4.448 | |
| 162 | +AP91,6.157 | |
| 163 | +AU03,5.314 | |
| 164 | +AO14,4.959 | |
| 165 | +AP94,6.625 | |
| 166 | +AU04,4.966 | |
| 167 | +AO15,6.165 | |
| 168 | +AP95,7.529 | |
| 169 | +AU05,4.992 | |
| 170 | +AO16,6.785 | |
| 171 | +AQ90,5.159 | |
| 172 | +AU06,4.826 | |
| 173 | +AO17,4.901 | |
| 174 | +AQ91,4.299 | |
| 175 | +AU07,5.176 | |
| 176 | +AO18,5.963 | |
| 177 | +AR50,4.883 | |
| 178 | +AU08,4.770 | |
| 179 | +AO19,5.050 | |
| 180 | +AR51,4.740 | |
| 181 | +AU09,6.266 | |
| 182 | +AO20,5.298 | |
| 183 | +AR52,4.061 | |
| 184 | +AU10,5.246 | |
| 185 | +AO21,4.879 | |
| 186 | +AR53,4.903 | |
| 187 | +AU11,5.286 | |
| 188 | +AO22,6.464 | |
| 189 | +AR54,4.670 | |
| 190 | +AU12,5.007 | |
| 191 | +AO23,5.026 | |
| 192 | +AR55,4.177 | |
| 193 | +AU13,5.020 | |
| 194 | +AO24,5.726 | |
| 195 | +AR56,5.396 | |
| 196 | +AU14,4.875 | |
| 197 | +AO25,4.922 | |
| 198 | +AR57,4.522 | |
| 199 | +AU15,5.126 | |
| 200 | +AO26,4.771 | |
| 201 | +AR58,4.563 | |
| 202 | +AU16,4.962 | |
| 203 | +AO27,4.699 | |
| 204 | +AR59,5.116 | |
| 205 | +AU17,5.000 | |
| 206 | +AO28,4.528 | |
| 207 | +AR60,4.579 | |
| 208 | +AU18,4.932 | |
| 209 | +AO29,4.863 | |
| 210 | +AR61,5.056 | |
| 211 | +AU19,5.320 | |
| 212 | +AO30,5.227 | |
| 213 | +AR62,4.912 | |
| 214 | +AU20,4.781 | |
| 215 | +AO31,5.275 | |
| 216 | +AR63,4.599 | |
| 217 | +AU21,5.341 | |
| 218 | +AO32,5.077 | |
| 219 | +AR64,5.017 | |
| 220 | +AU22,5.211 | |
| 221 | +AO33,4.899 | |
| 222 | +AR65,4.857 | |
| 223 | +AU23,5.281 | |
| 224 | +AO34,4.895 | |
| 225 | +AR66,4.949 | |
| 226 | +AU24,5.515 | |
| 227 | +AO35,5.753 | |
| 228 | +AR67,5.506 | |
| 229 | +AU25,4.986 | |
| 230 | +AO36,5.768 | |
| 231 | +AR68,4.960 | |
| 232 | +AU26,5.197 | |
| 233 | +AO37,5.165 | |
| 234 | +AR69,4.962 | |
| 235 | +AU27,5.603 | |
| 236 | +AO38,5.083 | |
| 237 | +AR71,5.887 | |
| 238 | +AU28,5.030 | |
| 239 | +AO39,5.176 | |
| 240 | +AR72,4.983 | |
| 241 | +AU29,5.078 | |
| 242 | +AO40,4.987 | |
| 243 | +AR73,5.193 | |
| 244 | +AU30,5.173 | |
| 245 | +AO41,5.104 | |
| 246 | +AR74,4.666 | |
| 247 | +AU31,4.974 | |
| 248 | +AU32,4.748 | |
| 249 | +BA11,5.328 | |
| 250 | +BR56,5.583 | |
| 251 | +AU33,5.473 | |
| 252 | +BA13,5.319 | |
| 253 | +BU50,4.918 | |
| 254 | +AU35,5.158 | |
| 255 | +BA31,5.393 | |
| 256 | +BU51,5.492 | |
| 257 | +AU36,4.937 | |
| 258 | +BC03,5.127 | |
| 259 | +BU52,4.787 | |
| 260 | +AU37,4.841 | |
| 261 | +BC04,5.629 | |
| 262 | +BU53,5.415 | |
| 263 | +AU38,4.830 | |
| 264 | +BC05,5.690 | |
| 265 | +BU54,5.468 | |
| 266 | +AU40,4.829 | |
| 267 | +BC06,4.959 | |
| 268 | +BU55,5.168 | |
| 269 | +AU44,5.503 | |
| 270 | +BC07,7.181 | |
| 271 | +BU56,4.975 | |
| 272 | +AU45,5.285 | |
| 273 | +BC08,5.252 | |
| 274 | +BU57,5.059 | |
| 275 | +AU47,5.035 | |
| 276 | +BC09,6.143 | |
| 277 | +BU58,5.544 | |
| 278 | +AU60,5.189 | |
| 279 | +BD01,6.200 | |
| 280 | +BU59,5.823 | |
| 281 | +AU61,5.015 | |
| 282 | +BD06,6.182 | |
| 283 | +BU60,4.964 | |
| 284 | +AU62,5.049 | |
| 285 | +BD07,7.544 | |
| 286 | +BU61,5.056 | |
| 287 | +AU63,5.135 | |
| 288 | +BE60,5.624 | |
| 289 | +BU63,4.858 | |
| 290 | +AU64,5.244 | |
| 291 | +BF00,5.419 | |
| 292 | +BU65,5.412 | |
| 293 | +AU65,5.205 | |
| 294 | +BG00,7.544 | |
| 295 | +BU72,5.439 | |
| 296 | +AU66,4.804 | |
| 297 | +BG06,7.544 | |
| 298 | +BU73,5.124 | |
| 299 | +AU67,4.776 | |
| 300 | +BI02,4.061 | |
| 301 | +BU75,4.880 | |
| 302 | +AU68,5.460 | |
| 303 | +BJ01,5.082 | |
| 304 | +BU81,4.895 | |
| 305 | +AU87,5.049 | |
| 306 | +BJ02,4.874 | |
| 307 | +BU85,6.186 | |
| 308 | +AV01,6.033 | |
| 309 | +BJ03,5.129 | |
| 310 | +BU86,4.962 | |
| 311 | +AV51,5.810 | |
| 312 | +BJ05,5.035 | |
| 313 | +BX00,4.759 | |
| 314 | +AV52,5.866 | |
| 315 | +BJ06,5.170 | |
| 316 | +BX02,4.856 | |
| 317 | +AV53,5.952 | |
| 318 | +BJ07,4.917 | |
| 319 | +BX30,5.180 | |
| 320 | +AX02,4.919 | |
| 321 | +BJ09,5.514 | |
| 322 | +BX32,4.943 | |
| 323 | +AX03,5.083 | |
| 324 | +BJ10,4.992 | |
| 325 | +BZ88,7.544 | |
| 326 | +AX04,4.755 | |
| 327 | +BJ15,5.559 | |
| 328 | +G203,4.926 | |
| 329 | +AX08,4.673 | |
| 330 | +BL00,4.978 | |
| 331 | +G204,5.081 | |
| 332 | +AX10,5.396 | |
| 333 | +BL01,5.192 | |
| 334 | +G501,5.090 | |
| 335 | +AX31,5.050 | |
| 336 | +BL02,5.821 | |
| 337 | +G503,5.395 | |
| 338 | +AX34,4.502 | |
| 339 | +BL40,4.689 | |
| 340 | +G506,5.121 | |
| 341 | +AX35,4.914 | |
| 342 | +BL41,5.274 | |
| 343 | +G507,5.030 | |
| 344 | +AY50,4.721 | |
| 345 | +BM03,5.577 | |
| 346 | +G509,4.893 | |
| 347 | +AY52,5.340 | |
| 348 | +BM04,5.609 | |
| 349 | +G510,5.173 | |
| 350 | +AY55,4.988 | |
| 351 | +BM05,5.650 | |
| 352 | +G511,4.951 | |
| 353 | +AY59,5.771 | |
| 354 | +BM06,6.208 | |
| 355 | +G512,4.978 | |
| 356 | +AY70,7.544 | |
| 357 | +BN06,4.954 | |
| 358 | +G513,5.190 | |
| 359 | +AY81,4.326 | |
| 360 | +BO00,4.837 | |
| 361 | +G514,5.030 | |
| 362 | +AY83,7.544 | |
| 363 | +BO01,5.159 | |
| 364 | +G515,4.969 | |
| 365 | +AY96,5.215 | |
| 366 | +BP15,4.061 | |
| 367 | +G516,5.326 | |
| 368 | +AY97,5.244 | |
| 369 | +BR50,4.464 | |
| 370 | +G518,5.274 | |
| 371 | +AY99,5.612 | |
| 372 | +BR51,4.756 | |
| 373 | +G520,5.080 | |
| 374 | +B800,4.061 | |
| 375 | +BR52,5.022 | |
| 376 | +G521,5.470 | |
| 377 | +B999,7.544 | |
| 378 | +BR53,4.571 | |
| 379 | +G522,5.912 | |
| 380 | +BA02,5.241 | |
| 381 | +BR54,5.054 | |
| 382 | +G524,5.143 | |
| 383 | +BA03,5.425 | |
| 384 | +BR55,5.190 | |
| 385 | +G525,5.394 | |
| 386 | +G526,5.203 | |
| 387 | +GC97,5.288 | |
| 388 | +GJ18,4.703 | |
| 389 | +G597,5.117 | |
| 390 | +GC98,4.061 | |
| 391 | +GJ19,5.034 | |
| 392 | +G598,5.233 | |
| 393 | +GC99,5.079 | |
| 394 | +GJ20,5.016 | |
| 395 | +G599,5.104 | |
| 396 | +GD01,7.174 | |
| 397 | +GJ21,4.720 | |
| 398 | +G600,5.408 | |
| 399 | +GD03,4.500 | |
| 400 | +GJ23,4.328 | |
| 401 | +G601,4.971 | |
| 402 | +GD05,6.166 | |
| 403 | +GJ24,5.135 | |
| 404 | +G602,4.734 | |
| 405 | +GD06,5.388 | |
| 406 | +GJ25,4.823 | |
| 407 | +G603,4.840 | |
| 408 | +GD08,4.061 | |
| 409 | +GJ28,4.856 | |
| 410 | +G604,4.817 | |
| 411 | +GD20,6.570 | |
| 412 | +GJ29,5.072 | |
| 413 | +G605,4.906 | |
| 414 | +GD22,6.888 | |
| 415 | +GJ31,4.672 | |
| 416 | +G606,4.822 | |
| 417 | +GD23,7.208 | |
| 418 | +GJ37,4.730 | |
| 419 | +G609,4.898 | |
| 420 | +GD24,6.025 | |
| 421 | +GJ39,6.424 | |
| 422 | +G610,4.735 | |
| 423 | +GD25,5.941 | |
| 424 | +GJ40,4.862 | |
| 425 | +G611,1.613 | |
| 426 | +GD50,6.499 | |
| 427 | +GJ41,4.609 | |
| 428 | +G612,4.999 | |
| 429 | +GD53,5.349 | |
| 430 | +GJ42,4.889 | |
| 431 | +G613,5.374 | |
| 432 | +GD54,6.593 | |
| 433 | +GJ43,4.454 | |
| 434 | +G614,5.082 | |
| 435 | +GD55,7.544 | |
| 436 | +GJ44,5.252 | |
| 437 | +G615,4.706 | |
| 438 | +GD57,6.474 | |
| 439 | +GJ46,4.936 | |
| 440 | +G616,4.848 | |
| 441 | +GE01,4.842 | |
| 442 | +GJ47,5.136 | |
| 443 | +G800,4.061 | |
| 444 | +GE02,5.046 | |
| 445 | +GJ48,4.593 | |
| 446 | +G999,7.544 | |
| 447 | +GE03,5.207 | |
| 448 | +GJ49,4.771 | |
| 449 | +GA01,5.446 | |
| 450 | +GE04,4.205 | |
| 451 | +GL02,5.083 | |
| 452 | +GA03,5.620 | |
| 453 | +GE99,5.088 | |
| 454 | +GL13,5.082 | |
| 455 | +GA04,5.431 | |
| 456 | +GF00,5.541 | |
| 457 | +GJ31,4.937 | |
| 458 | +GA06,5.745 | |
| 459 | +GI02,6.155 | |
| 460 | +GL33,4.767 | |
| 461 | +GA11,5.391 | |
| 462 | +GI40,5.020 | |
| 463 | +GL34,5.094 | |
| 464 | +GA14,5.131 | |
| 465 | +GI70,6.246 | |
| 466 | +GL35,5.448 | |
| 467 | +GA21,5.254 | |
| 468 | +GI71,6.573 | |
| 469 | +GL36,5.107 | |
| 470 | +GA24,6.472 | |
| 471 | +GI73,6.329 | |
| 472 | +GL40,4.061 | |
| 473 | +GC01,6.828 | |
| 474 | +GJ00,4.849 | |
| 475 | +GL50,4.691 | |
| 476 | +GC02,5.385 | |
| 477 | +GJ01,4.456 | |
| 478 | +GL60,5.158 | |
| 479 | +GC03,5.909 | |
| 480 | +GJ02,4.893 | |
| 481 | +GM00,6.185 | |
| 482 | +GC04,5.943 | |
| 483 | +GJ03,4.921 | |
| 484 | +GM01,5.523 | |
| 485 | +GC23,7.458 | |
| 486 | +GJ04,4.831 | |
| 487 | +GM02,5.867 | |
| 488 | +GC37,7.544 | |
| 489 | +GJ05,5.454 | |
| 490 | +GM03,5.544 | |
| 491 | +GC80,5.225 | |
| 492 | +GJ06,5.036 | |
| 493 | +GM05,5.829 | |
| 494 | +GC81,5.763 | |
| 495 | +GJ07,5.289 | |
| 496 | +GM10,5.697 | |
| 497 | +GC82,7.143 | |
| 498 | +GJ08,5.306 | |
| 499 | +GM11,6.534 | |
| 500 | +GC85,5.376 | |
| 501 | +GJ09,4.566 | |
| 502 | +GM12,5.613 | |
| 503 | +GC86,5.050 | |
| 504 | +GJ10,4.864 | |
| 505 | +GM13,5.955 | |
| 506 | +GC87,5.244 | |
| 507 | +GJ11,4.685 | |
| 508 | +GM14,5.876 | |
| 509 | +GC88,5.372 | |
| 510 | +GJ12,4.977 | |
| 511 | +GM15,5.738 | |
| 512 | +GC89,6.479 | |
| 513 | +GJ13,4.688 | |
| 514 | +GM16,5.231 | |
| 515 | +GC91,5.218 | |
| 516 | +GJ15,5.159 | |
| 517 | +GM17,5.359 | |
| 518 | +GC94,5.622 | |
| 519 | +GJ16,5.038 | |
| 520 | +GM18,5.974 | |
| 521 | +GC95,6.323 | |
| 522 | +GJ17,4.925 | |
| 523 | +GM19,6.091 | |
| 524 | +GM20,5.602 | |
| 525 | +GO26,6.609 | |
| 526 | +GP38,4.061 | |
| 527 | +GM21,5.484 | |
| 528 | +GO27,5.616 | |
| 529 | +GP39,4.207 | |
| 530 | +GM22,5.564 | |
| 531 | +GO28,5.764 | |
| 532 | +GP53,5.934 | |
| 533 | +GM23,5.657 | |
| 534 | +GO29,7.025 | |
| 535 | +GP56,7.544 | |
| 536 | +GM24,5.352 | |
| 537 | +GO31,4.610 | |
| 538 | +GP88,4.061 | |
| 539 | +GM25,5.604 | |
| 540 | +GO32,5.555 | |
| 541 | +GP89,6.164 | |
| 542 | +GM26,5.475 | |
| 543 | +GO34,5.278 | |
| 544 | +GP98,5.983 | |
| 545 | +GM28,5.201 | |
| 546 | +GO35,4.799 | |
| 547 | +GQ90,5.108 | |
| 548 | +GM30,5.549 | |
| 549 | +GO36,5.212 | |
| 550 | +GR50,5.447 | |
| 551 | +GM31,5.311 | |
| 552 | +GO37,4.661 | |
| 553 | +GR51,4.740 | |
| 554 | +GM32,4.995 | |
| 555 | +GO38,5.174 | |
| 556 | +GR52,5.012 | |
| 557 | +GM33,5.413 | |
| 558 | +GO39,6.541 | |
| 559 | +GR53,5.070 | |
| 560 | +GM34,4.925 | |
| 561 | +GO40,6.552 | |
| 562 | +GR54,5.263 | |
| 563 | +GM35,5.088 | |
| 564 | +GO42,4.489 | |
| 565 | +GR55,4.762 | |
| 566 | +GM36,5.408 | |
| 567 | +GO43,5.155 | |
| 568 | +GR56,5.067 | |
| 569 | +GM37,5.510 | |
| 570 | +GO44,5.965 | |
| 571 | +GR57,5.166 | |
| 572 | +GM38,4.923 | |
| 573 | +GO47,5.818 | |
| 574 | +GR58,5.128 | |
| 575 | +GM39,5.520 | |
| 576 | +GO49,5.877 | |
| 577 | +GR59,4.743 | |
| 578 | +GM44,5.239 | |
| 579 | +GO51,5.487 | |
| 580 | +GR60,5.585 | |
| 581 | +GM46,5.073 | |
| 582 | +GO53,6.016 | |
| 583 | +GR61,6.784 | |
| 584 | +GM49,5.491 | |
| 585 | +GO54,5.082 | |
| 586 | +GR62,5.229 | |
| 587 | +GN00,6.736 | |
| 588 | +GO55,4.677 | |
| 589 | +GR63,4.875 | |
| 590 | +GN47,7.544 | |
| 591 | +GO56,5.522 | |
| 592 | +GR64,5.515 | |
| 593 | +GN64,7.544 | |
| 594 | +GO57,6.081 | |
| 595 | +GR65,4.993 | |
| 596 | +GN71,7.544 | |
| 597 | +GO58,5.320 | |
| 598 | +GR66,4.797 | |
| 599 | +GO01,5.336 | |
| 600 | +GO59,5.469 | |
| 601 | +GR67,5.119 | |
| 602 | +GO02,4.531 | |
| 603 | +GO60,6.222 | |
| 604 | +GR68,5.028 | |
| 605 | +GO03,4.801 | |
| 606 | +GO61,5.837 | |
| 607 | +GR69,5.431 | |
| 608 | +GO04,5.332 | |
| 609 | +GO62,4.502 | |
| 610 | +GR70,4.857 | |
| 611 | +GO05,4.739 | |
| 612 | +GO63,5.125 | |
| 613 | +GR71,4.751 | |
| 614 | +GO06,4.723 | |
| 615 | +GO64,5.114 | |
| 616 | +GR72,5.157 | |
| 617 | +GO07,4.850 | |
| 618 | +GO65,6.212 | |
| 619 | +GR73,4.996 | |
| 620 | +GO08,4.678 | |
| 621 | +GO66,5.729 | |
| 622 | +GR74,5.167 | |
| 623 | +GO09,5.883 | |
| 624 | +GO68,4.828 | |
| 625 | +GR75,4.875 | |
| 626 | +GO11,5.459 | |
| 627 | +GO69,5.174 | |
| 628 | +GR76,5.115 | |
| 629 | +GO12,4.849 | |
| 630 | +GO95,5.631 | |
| 631 | +GR77,5.208 | |
| 632 | +GO13,5.253 | |
| 633 | +GO96,5.005 | |
| 634 | +GR78,5.060 | |
| 635 | +GO16,4.565 | |
| 636 | +GO97,4.836 | |
| 637 | +GR79,4.673 | |
| 638 | +GO17,5.299 | |
| 639 | +GO98,4.934 | |
| 640 | +GR80,4.874 | |
| 641 | +GO19,4.600 | |
| 642 | +GO99,5.852 | |
| 643 | +GR81,4.834 | |
| 644 | +GO20,4.991 | |
| 645 | +GP01,6.786 | |
| 646 | +GR82,5.022 | |
| 647 | +GO21,4.510 | |
| 648 | +GP07,4.061 | |
| 649 | +GU01,5.165 | |
| 650 | +GO22,5.645 | |
| 651 | +GP08,5.746 | |
| 652 | +GU02,5.238 | |
| 653 | +GO23,5.475 | |
| 654 | +GP23,7.544 | |
| 655 | +GU03,5.298 | |
| 656 | +GO24,4.990 | |
| 657 | +GP32,4.061 | |
| 658 | +GU04,5.188 | |
| 659 | +GO25,5.602 | |
| 660 | +GP37,7.544 | |
| 661 | +GU05,5.239 | |
| 662 | +GU06,4.786 | |
| 663 | +GU62,5.824 | |
| 664 | +GX22,5.120 | |
| 665 | +GU07,5.256 | |
| 666 | +GU63,5.475 | |
| 667 | +GX24,4.978 | |
| 668 | +GU08,5.212 | |
| 669 | +GU64,5.206 | |
| 670 | +GX26,4.818 | |
| 671 | +GU10,5.155 | |
| 672 | +GU65,5.578 | |
| 673 | +GX28,5.249 | |
| 674 | +GU11,5.039 | |
| 675 | +GU66,5.064 | |
| 676 | +GX29,5.094 | |
| 677 | +GU12,4.815 | |
| 678 | +GU67,5.338 | |
| 679 | +GX31,6.114 | |
| 680 | +GU13,5.782 | |
| 681 | +GU68,5.059 | |
| 682 | +GX32,4.898 | |
| 683 | +GU14,5.119 | |
| 684 | +GU69,5.580 | |
| 685 | +GX34,4.932 | |
| 686 | +GU15,5.043 | |
| 687 | +GU70,4.978 | |
| 688 | +GX37,5.973 | |
| 689 | +GU16,4.846 | |
| 690 | +GU71,5.697 | |
| 691 | +GX40,4.940 | |
| 692 | +GU18,5.002 | |
| 693 | +GU72,4.900 | |
| 694 | +GX41,5.116 | |
| 695 | +GU19,5.019 | |
| 696 | +GU73,5.555 | |
| 697 | +GX42,4.888 | |
| 698 | +GU20,5.350 | |
| 699 | +GU74,5.083 | |
| 700 | +GX43,5.979 | |
| 701 | +GU21,4.909 | |
| 702 | +GU75,5.395 | |
| 703 | +GX45,5.953 | |
| 704 | +GU23,5.700 | |
| 705 | +GU76,5.014 | |
| 706 | +GX46,5.040 | |
| 707 | +GU24,5.410 | |
| 708 | +GU77,5.411 | |
| 709 | +GX48,5.495 | |
| 710 | +GU25,5.418 | |
| 711 | +GU78,5.005 | |
| 712 | +GY51,6.719 | |
| 713 | +GU26,4.946 | |
| 714 | +GU79,5.473 | |
| 715 | +GY66,6.029 | |
| 716 | +GU27,5.442 | |
| 717 | +GU80,5.326 | |
| 718 | +GY70,6.643 | |
| 719 | +GU28,5.307 | |
| 720 | +GU82,5.007 | |
| 721 | +GY72,6.099 | |
| 722 | +GU29,4.982 | |
| 723 | +GU83,5.351 | |
| 724 | +GY74,5.553 | |
| 725 | +GU30,5.283 | |
| 726 | +GU84,4.730 | |
| 727 | +GY99,7.544 | |
| 728 | +GU32,5.406 | |
| 729 | +GU86,5.081 | |
| 730 | +GZ02,6.276 | |
| 731 | +GU34,5.014 | |
| 732 | +GU87,4.647 | |
| 733 | +GZ88,7.544 | |
| 734 | +GU35,5.163 | |
| 735 | +GU89,5.127 | |
| 736 | +H999,7.544 | |
| 737 | +GU36,5.559 | |
| 738 | +GU90,5.202 | |
| 739 | +HC02,7.520 | |
| 740 | +GU37,5.018 | |
| 741 | +GU91,5.106 | |
| 742 | +HC05,5.096 | |
| 743 | +GU38,5.215 | |
| 744 | +GU92,5.270 | |
| 745 | +HC16,5.900 | |
| 746 | +GU39,5.299 | |
| 747 | +GU93,5.454 | |
| 748 | +HC21,6.101 | |
| 749 | +GU40,5.251 | |
| 750 | +GU95,5.195 | |
| 751 | +HC22,5.322 | |
| 752 | +GU41,5.270 | |
| 753 | +GU96,5.291 | |
| 754 | +HC30,7.544 | |
| 755 | +GU42,5.375 | |
| 756 | +GU97,5.005 | |
| 757 | +HC31,5.173 | |
| 758 | +GU44,4.496 | |
| 759 | +GU98,4.989 | |
| 760 | +HC34,6.747 | |
| 761 | +GU45,5.435 | |
| 762 | +GU99,4.989 | |
| 763 | +HC37,7.144 | |
| 764 | +GU46,5.547 | |
| 765 | +GV00,5.843 | |
| 766 | +HC38,5.887 | |
| 767 | +GU47,5.256 | |
| 768 | +GV50,6.849 | |
| 769 | +HC39,7.544 | |
| 770 | +GU48,5.063 | |
| 771 | +GV52,5.714 | |
| 772 | +HC42,6.415 | |
| 773 | +GU49,4.837 | |
| 774 | +GV53,5.451 | |
| 775 | +HC43,5.786 | |
| 776 | +GU51,5.033 | |
| 777 | +GV56,5.711 | |
| 778 | +HC46,5.755 | |
| 779 | +GU52,5.314 | |
| 780 | +GV58,5.766 | |
| 781 | +HC47,5.980 | |
| 782 | +GU53,5.136 | |
| 783 | +GV60,5.706 | |
| 784 | +HC68,5.276 | |
| 785 | +GU56,4.974 | |
| 786 | +GX01,5.771 | |
| 787 | +HC71,7.544 | |
| 788 | +GU57,5.052 | |
| 789 | +GX02,4.873 | |
| 790 | +HC72,7.544 | |
| 791 | +GU58,5.184 | |
| 792 | +GX06,4.999 | |
| 793 | +HC81,5.256 | |
| 794 | +GU60,5.081 | |
| 795 | +GX20,5.212 | |
| 796 | +HC83,5.422 | |
| 797 | +GU61,5.492 | |
| 798 | +GX21,5.256 | |
| 799 | +HC84,5.189 | |
| 800 | +HC85,5.242 | |
| 801 | +HN30,4.292 | |
| 802 | +HO45,4.888 | |
| 803 | +HC86,4.610 | |
| 804 | +HN88,5.416 | |
| 805 | +HO46,4.672 | |
| 806 | +HC89,4.691 | |
| 807 | +HN89,5.412 | |
| 808 | +HO47,5.708 | |
| 809 | +HC91,5.668 | |
| 810 | +HO01,6.010 | |
| 811 | +HO48,4.998 | |
| 812 | +HC92,5.248 | |
| 813 | +HO02,5.700 | |
| 814 | +HO49,4.621 | |
| 815 | +HC95,5.202 | |
| 816 | +HO03,4.645 | |
| 817 | +HO51,4.156 | |
| 818 | +HD10,7.544 | |
| 819 | +HO04,6.297 | |
| 820 | +HO52,5.949 | |
| 821 | +HG04,5.488 | |
| 822 | +HO05,5.693 | |
| 823 | +HO54,5.586 | |
| 824 | +HJ00,5.190 | |
| 825 | +HO06,4.829 | |
| 826 | +HO55,6.240 | |
| 827 | +HJ02,4.764 | |
| 828 | +HO07,5.738 | |
| 829 | +HO56,6.075 | |
| 830 | +HJ04,4.939 | |
| 831 | +HO08,6.385 | |
| 832 | +HO57,4.578 | |
| 833 | +HJ05,5.336 | |
| 834 | +HO09,5.846 | |
| 835 | +HO58,5.328 | |
| 836 | +HJ19,5.324 | |
| 837 | +HO10,6.813 | |
| 838 | +HO59,4.975 | |
| 839 | +HJ28,4.850 | |
| 840 | +HO11,6.386 | |
| 841 | +HO60,5.627 | |
| 842 | +HJ29,5.251 | |
| 843 | +HO12,5.652 | |
| 844 | +HO61,5.382 | |
| 845 | +HJ31,5.304 | |
| 846 | +HO13,4.716 | |
| 847 | +HO62,5.894 | |
| 848 | +HJ35,5.405 | |
| 849 | +HO14,4.590 | |
| 850 | +HO63,5.829 | |
| 851 | +HM01,5.465 | |
| 852 | +HO15,4.662 | |
| 853 | +HO64,5.570 | |
| 854 | +HM03,5.353 | |
| 855 | +HO17,5.237 | |
| 856 | +HO65,4.800 | |
| 857 | +HM05,6.307 | |
| 858 | +HO18,5.204 | |
| 859 | +HO67,6.519 | |
| 860 | +HM06,5.389 | |
| 861 | +HO19,4.470 | |
| 862 | +HO68,5.849 | |
| 863 | +HM09,5.582 | |
| 864 | +HO20,4.602 | |
| 865 | +HO69,6.975 | |
| 866 | +HM11,5.123 | |
| 867 | +HO21,5.029 | |
| 868 | +HO70,4.503 | |
| 869 | +HM16,5.288 | |
| 870 | +HO22,4.925 | |
| 871 | +HO71,5.987 | |
| 872 | +HM17,4.894 | |
| 873 | +HO23,4.640 | |
| 874 | +HO72,5.059 | |
| 875 | +HM18,5.574 | |
| 876 | +HO24,4.890 | |
| 877 | +HO73,6.322 | |
| 878 | +HM21,5.263 | |
| 879 | +HO25,5.006 | |
| 880 | +HO74,5.278 | |
| 881 | +HM22,5.519 | |
| 882 | +HO26,5.046 | |
| 883 | +HO75,4.586 | |
| 884 | +HM24,5.282 | |
| 885 | +HO27,5.265 | |
| 886 | +HO76,4.776 | |
| 887 | +HM25,5.498 | |
| 888 | +HO28,5.114 | |
| 889 | +HO77,7.071 | |
| 890 | +HM26,5.607 | |
| 891 | +HO29,5.153 | |
| 892 | +HO78,5.638 | |
| 893 | +HM29,5.462 | |
| 894 | +HO30,5.813 | |
| 895 | +HO79,5.306 | |
| 896 | +HM30,5.874 | |
| 897 | +HO31,5.508 | |
| 898 | +HO80,6.973 | |
| 899 | +HM31,6.156 | |
| 900 | +HO32,5.325 | |
| 901 | +HO82,5.315 | |
| 902 | +HM34,5.249 | |
| 903 | +HO33,4.727 | |
| 904 | +HO84,4.946 | |
| 905 | +HM37,5.301 | |
| 906 | +HO35,4.795 | |
| 907 | +HO86,6.264 | |
| 908 | +HM41,4.672 | |
| 909 | +HO36,5.038 | |
| 910 | +HO87,7.340 | |
| 911 | +HM43,4.516 | |
| 912 | +HO37,4.632 | |
| 913 | +HO88,6.115 | |
| 914 | +HM46,5.120 | |
| 915 | +HO38,4.759 | |
| 916 | +HP05,5.493 | |
| 917 | +HM47,4.998 | |
| 918 | +HO39,6.787 | |
| 919 | +HP19,5.585 | |
| 920 | +HM48,5.499 | |
| 921 | +HO40,6.242 | |
| 922 | +HP21,6.273 | |
| 923 | +HM49,5.054 | |
| 924 | +HO41,5.408 | |
| 925 | +HP24,6.722 | |
| 926 | +HN07,4.110 | |
| 927 | +HO42,6.595 | |
| 928 | +HP29,7.388 | |
| 929 | +HN14,5.139 | |
| 930 | +HO43,5.913 | |
| 931 | +HP31,6.625 | |
| 932 | +HN23,7.544 | |
| 933 | +HO44,4.286 | |
| 934 | +HP32,6.625 | |
| 935 | +HP38,6.625 | |
| 936 | +HU75,5.877 | |
| 937 | +MC02,5.861 | |
| 938 | +HP39,7.518 | |
| 939 | +HU77,5.380 | |
| 940 | +MC04,5.847 | |
| 941 | +HP42,4.061 | |
| 942 | +HU78,5.736 | |
| 943 | +MC05,6.404 | |
| 944 | +HP83,6.715 | |
| 945 | +HV50,7.350 | |
| 946 | +MC09,5.937 | |
| 947 | +HP94,5.823 | |
| 948 | +HX03,5.637 | |
| 949 | +MC16,5.649 | |
| 950 | +HR50,5.138 | |
| 951 | +HX04,5.498 | |
| 952 | +MD51,5.425 | |
| 953 | +HR52,5.350 | |
| 954 | +HX07,5.793 | |
| 955 | +MD56,7.544 | |
| 956 | +HR53,5.697 | |
| 957 | +HX09,6.665 | |
| 958 | +MD67,7.544 | |
| 959 | +HR54,5.109 | |
| 960 | +HX11,5.523 | |
| 961 | +ME00,5.017 | |
| 962 | +HR55,5.187 | |
| 963 | +HX14,5.559 | |
| 964 | +ME51,5.219 | |
| 965 | +HR56,5.212 | |
| 966 | +HX19,5.619 | |
| 967 | +ME52,5.549 | |
| 968 | +HR57,5.332 | |
| 969 | +HX21,5.489 | |
| 970 | +MG01,4.097 | |
| 971 | +HR58,5.688 | |
| 972 | +HX24,5.264 | |
| 973 | +MI02,4.061 | |
| 974 | +HR61,5.373 | |
| 975 | +HX34,6.638 | |
| 976 | +MJ02,5.933 | |
| 977 | +HR63,4.984 | |
| 978 | +HX61,5.480 | |
| 979 | +MJ03,5.291 | |
| 980 | +HR64,5.038 | |
| 981 | +HX65,5.707 | |
| 982 | +MJ05,4.741 | |
| 983 | +HR66,6.585 | |
| 984 | +HX67,5.682 | |
| 985 | +MJ06,5.189 | |
| 986 | +HR72,5.156 | |
| 987 | +HX73,5.447 | |
| 988 | +MJ08,5.094 | |
| 989 | +HR80,5.011 | |
| 990 | +HX77,5.151 | |
| 991 | +MJ09,4.862 | |
| 992 | +HR81,5.012 | |
| 993 | +HX96,5.364 | |
| 994 | +MJ10,5.020 | |
| 995 | +HR82,4.952 | |
| 996 | +HY52,5.884 | |
| 997 | +MJ16,4.856 | |
| 998 | +HR83,5.013 | |
| 999 | +HY58,5.492 | |
| 1000 | +MJ41,4.824 | |
| 1001 | +HR84,5.289 | |
| 1002 | +HY59,5.439 | |
| 1003 | +MJ42,4.655 | |
| 1004 | +HR85,4.851 | |
| 1005 | +HY60,6.205 | |
| 1006 | +MJ44,4.991 | |
| 1007 | +HU01,5.071 | |
| 1008 | +HY61,6.740 | |
| 1009 | +ML01,5.153 | |
| 1010 | +HU02,5.464 | |
| 1011 | +HY62,5.803 | |
| 1012 | +ML02,5.215 | |
| 1013 | +HU06,5.690 | |
| 1014 | +HY63,5.680 | |
| 1015 | +ML06,5.622 | |
| 1016 | +HU16,5.626 | |
| 1017 | +HY67,6.237 | |
| 1018 | +ML08,5.651 | |
| 1019 | +HU17,5.714 | |
| 1020 | +HY86,5.544 | |
| 1021 | +ML09,5.732 | |
| 1022 | +HU19,5.379 | |
| 1023 | +HY87,7.097 | |
| 1024 | +ML13,5.681 | |
| 1025 | +HU21,5.420 | |
| 1026 | +HY88,5.701 | |
| 1027 | +ML40,5.821 | |
| 1028 | +HU22,5.453 | |
| 1029 | +HY89,7.544 | |
| 1030 | +ML41,5.386 | |
| 1031 | +HU33,5.556 | |
| 1032 | +HY91,5.681 | |
| 1033 | +ML42,5.574 | |
| 1034 | +HU36,5.547 | |
| 1035 | +HY98,4.582 | |
| 1036 | +ML44,5.516 | |
| 1037 | +HU39,4.960 | |
| 1038 | +HZ88,7.544 | |
| 1039 | +ML46,5.544 | |
| 1040 | +HU42,5.230 | |
| 1041 | +M300,4.992 | |
| 1042 | +MM03,5.565 | |
| 1043 | +HU45,5.207 | |
| 1044 | +M301,5.226 | |
| 1045 | +MM06,5.466 | |
| 1046 | +HU47,5.305 | |
| 1047 | +M309,6.883 | |
| 1048 | +MN90,5.420 | |
| 1049 | +HU48,5.445 | |
| 1050 | +M402,5.557 | |
| 1051 | +MO01,5.433 | |
| 1052 | +HU49,5.201 | |
| 1053 | +M800,4.061 | |
| 1054 | +MO02,5.325 | |
| 1055 | +HU50,5.533 | |
| 1056 | +M999,7.544 | |
| 1057 | +MO03,4.871 | |
| 1058 | +HU51,6.127 | |
| 1059 | +MX01,5.857 | |
| 1060 | +MP15,4.061 | |
| 1061 | +HU52,5.335 | |
| 1062 | +MA02,5.395 | |
| 1063 | +MP30,5.930 | |
| 1064 | +HU64,5.529 | |
| 1065 | +MA04,5.516 | |
| 1066 | +MR50,4.521 | |
| 1067 | +HU70,5.105 | |
| 1068 | +MC00,6.164 | |
| 1069 | +MR51,4.710 | |
| 1070 | +HU72,5.173 | |
| 1071 | +MC01,7.544 | |
| 1072 | +MR52,4.526 | |
| 1073 | +MU01,4.966 | |
| 1074 | +MU15,4.851 | |
| 1075 | +MX02,5.003 | |
| 1076 | +MU02,5.344 | |
| 1077 | +MU26,5.311 | |
| 1078 | +MX03,5.353 | |
| 1079 | +MU05,5.094 | |
| 1080 | +MU27,5.196 | |
| 1081 | +MX04,5.194 | |
| 1082 | +MU08,4.651 | |
| 1083 | +MU38,4.963 | |
| 1084 | +MX10,5.108 | |
| 1085 | +MU09,5.226 | |
| 1086 | +MU39,5.147 | |
| 1087 | +MX33,5.155 | |
| 1088 | +MU10,4.954 | |
| 1089 | +MU40,5.043 | |
| 1090 | +MX37,5.268 | |
| 1091 | +MU14,5.150 | |
| 1092 | +MX00,4.942 | |
| 1093 | +MZ88,7.544 | |
modified
data/synthetic/README.md
+3 −0
@@ -64,3 +64,6 @@ Les relations entre variables (élasticités, primes de secteur, décote d'ancie | ||
| 64 | 64 | | loyers_schl.rmr_trois_rivieres.taux_inoccupation#tous | 0.027 | 2.7 | proportion | SRC-E-007 | octobre 2025 (Enquête sur les logements locatifs, publiée 2025-12-11) | calibre | % → proportion | |
| 65 | 65 | | loyers_schl.rmr_saguenay.loyer_moyen#2_chambres | 985 | 985 | $/mois | SRC-E-007 | octobre 2025 (Enquête sur les logements locatifs, publiée 2025-12-11) | calibre | loyer moyen 2 ch. RMR saguenay (SCHL oct. 2025) | |
| 66 | 66 | | loyers_schl.rmr_saguenay.taux_inoccupation#tous | 0.013 | 1.3 | proportion | SRC-E-007 | octobre 2025 (Enquête sur les logements locatifs, publiée 2025-12-11) | calibre | % → proportion | |
| 67 | + | |
| 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). | |
added
data/synthetic/ch08_zonage_lots.csv
+401 −0
@@ -0,0 +1,401 @@ | ||
| 1 | +id_lot,x,y,zone_initiale,zone_apres_T0,rezone,superficie_terrain_m2,superficie_habitable_m2,age_2026,logements | |
| 2 | +L0000,0,0,C,C,False,743.0,,58,0 | |
| 3 | +L0001,1,0,C,C,False,709.0,,36,0 | |
| 4 | +L0002,2,0,C,C,False,793.0,,16,0 | |
| 5 | +L0003,3,0,C,C,False,793.0,,54,0 | |
| 6 | +L0004,4,0,C,C,False,757.0,,53,0 | |
| 7 | +L0005,5,0,C,C,False,812.0,,43,0 | |
| 8 | +L0006,6,0,C,C,False,796.0,,10,0 | |
| 9 | +L0007,7,0,C,C,False,821.0,,51,0 | |
| 10 | +L0008,8,0,C,C,False,708.0,,34,0 | |
| 11 | +L0009,9,0,C,C,False,711.0,,33,0 | |
| 12 | +L0010,10,0,C,C,False,740.0,,10,0 | |
| 13 | +L0011,11,0,C,C,False,837.0,,60,0 | |
| 14 | +L0012,12,0,C,C,False,714.0,,52,0 | |
| 15 | +L0013,13,0,C,C,False,809.0,,5,0 | |
| 16 | +L0014,14,0,C,C,False,778.0,,34,0 | |
| 17 | +L0015,15,0,C,C,False,696.0,,50,0 | |
| 18 | +L0016,16,0,C,C,False,768.0,,33,0 | |
| 19 | +L0017,17,0,C,C,False,752.0,,57,0 | |
| 20 | +L0018,18,0,C,C,False,775.0,,38,0 | |
| 21 | +L0019,19,0,C,C,False,780.0,,50,0 | |
| 22 | +L0100,0,1,C,C,False,716.0,,21,0 | |
| 23 | +L0101,1,1,C,C,False,750.0,,38,0 | |
| 24 | +L0102,2,1,C,C,False,785.0,,55,0 | |
| 25 | +L0103,3,1,C,C,False,804.0,,30,0 | |
| 26 | +L0104,4,1,C,C,False,724.0,,56,0 | |
| 27 | +L0105,5,1,C,C,False,744.0,,5,0 | |
| 28 | +L0106,6,1,C,C,False,742.0,,38,0 | |
| 29 | +L0107,7,1,C,C,False,789.0,,24,0 | |
| 30 | +L0108,8,1,C,C,False,749.0,,55,0 | |
| 31 | +L0109,9,1,C,C,False,753.0,,43,0 | |
| 32 | +L0110,10,1,C,C,False,698.0,,6,0 | |
| 33 | +L0111,11,1,C,C,False,766.0,,58,0 | |
| 34 | +L0112,12,1,C,C,False,817.0,,38,0 | |
| 35 | +L0113,13,1,C,C,False,721.0,,18,0 | |
| 36 | +L0114,14,1,C,C,False,832.0,,9,0 | |
| 37 | +L0115,15,1,C,C,False,707.0,,26,0 | |
| 38 | +L0116,16,1,C,C,False,711.0,,8,0 | |
| 39 | +L0117,17,1,C,C,False,775.0,,19,0 | |
| 40 | +L0118,18,1,C,C,False,701.0,,27,0 | |
| 41 | +L0119,19,1,C,C,False,797.0,,21,0 | |
| 42 | +L0200,0,2,C,C,False,733.0,,21,0 | |
| 43 | +L0201,1,2,C,C,False,803.0,,16,0 | |
| 44 | +L0202,2,2,C,C,False,724.0,,43,0 | |
| 45 | +L0203,3,2,C,C,False,837.0,,38,0 | |
| 46 | +L0204,4,2,C,C,False,792.0,,27,0 | |
| 47 | +L0205,5,2,C,C,False,738.0,,58,0 | |
| 48 | +L0206,6,2,C,C,False,767.0,,38,0 | |
| 49 | +L0207,7,2,C,C,False,758.0,,60,0 | |
| 50 | +L0208,8,2,C,C,False,748.0,,24,0 | |
| 51 | +L0209,9,2,C,C,False,769.0,,12,0 | |
| 52 | +L0210,10,2,C,C,False,805.0,,23,0 | |
| 53 | +L0211,11,2,C,C,False,771.0,,59,0 | |
| 54 | +L0212,12,2,C,C,False,719.0,,21,0 | |
| 55 | +L0213,13,2,C,C,False,735.0,,14,0 | |
| 56 | +L0214,14,2,C,C,False,802.0,,46,0 | |
| 57 | +L0215,15,2,C,C,False,768.0,,56,0 | |
| 58 | +L0216,16,2,C,C,False,776.0,,23,0 | |
| 59 | +L0217,17,2,C,C,False,743.0,,57,0 | |
| 60 | +L0218,18,2,C,C,False,782.0,,55,0 | |
| 61 | +L0219,19,2,C,C,False,835.0,,26,0 | |
| 62 | +L0300,0,3,P,P,False,590.0,,52,0 | |
| 63 | +L0301,1,3,P,P,False,629.0,,45,0 | |
| 64 | +L0302,2,3,P,P,False,669.0,,24,0 | |
| 65 | +L0303,3,3,P,P,False,563.0,,20,0 | |
| 66 | +L0304,4,3,H1,H1,False,614.0,167.0,36,1 | |
| 67 | +L0305,5,3,H1,H1,False,571.0,160.0,73,1 | |
| 68 | +L0306,6,3,H1,H1,False,600.0,147.0,27,1 | |
| 69 | +L0307,7,3,H1,H1,False,562.0,145.0,39,1 | |
| 70 | +L0308,8,3,H1,H1,False,645.0,192.0,65,1 | |
| 71 | +L0309,9,3,H1,H1,False,611.0,147.0,63,1 | |
| 72 | +L0310,10,3,H1,H1,False,629.0,169.0,57,1 | |
| 73 | +L0311,11,3,H1,H1,False,647.0,150.0,41,1 | |
| 74 | +L0312,12,3,H1,H1,False,574.0,117.0,39,1 | |
| 75 | +L0313,13,3,H1,H1,False,638.0,150.0,56,1 | |
| 76 | +L0314,14,3,H1,H1,False,663.0,119.0,28,1 | |
| 77 | +L0315,15,3,H1,H1,False,650.0,101.0,73,1 | |
| 78 | +L0316,16,3,H1,H1,False,629.0,115.0,52,1 | |
| 79 | +L0317,17,3,H1,H1,False,588.0,117.0,71,1 | |
| 80 | +L0318,18,3,H1,H1,False,567.0,121.0,49,1 | |
| 81 | +L0319,19,3,H1,H1,False,636.0,102.0,15,1 | |
| 82 | +L0400,0,4,P,P,False,655.0,,5,0 | |
| 83 | +L0401,1,4,P,P,False,572.0,,29,0 | |
| 84 | +L0402,2,4,P,P,False,653.0,,28,0 | |
| 85 | +L0403,3,4,P,P,False,567.0,,20,0 | |
| 86 | +L0404,4,4,H1,H1,False,552.0,118.0,43,1 | |
| 87 | +L0405,5,4,H1,H1,False,559.0,164.0,16,1 | |
| 88 | +L0406,6,4,H1,H1,False,591.0,152.0,25,1 | |
| 89 | +L0407,7,4,H1,H1,False,597.0,103.0,14,1 | |
| 90 | +L0408,8,4,H1,H1,False,601.0,207.0,24,1 | |
| 91 | +L0409,9,4,H1,H1,False,576.0,151.0,50,1 | |
| 92 | +L0410,10,4,H1,H1,False,554.0,177.0,47,1 | |
| 93 | +L0411,11,4,H1,H1,False,559.0,181.0,44,1 | |
| 94 | +L0412,12,4,H1,H1,False,617.0,130.0,13,1 | |
| 95 | +L0413,13,4,H1,H1,False,628.0,135.0,14,1 | |
| 96 | +L0414,14,4,H1,H1,False,670.0,124.0,36,1 | |
| 97 | +L0415,15,4,H1,H1,False,562.0,134.0,43,1 | |
| 98 | +L0416,16,4,H1,H1,False,582.0,113.0,30,1 | |
| 99 | +L0417,17,4,H1,H1,False,599.0,115.0,33,1 | |
| 100 | +L0418,18,4,H1,H1,False,626.0,146.0,52,1 | |
| 101 | +L0419,19,4,H1,H1,False,649.0,114.0,35,1 | |
| 102 | +L0500,0,5,P,P,False,617.0,,59,0 | |
| 103 | +L0501,1,5,P,P,False,652.0,,44,0 | |
| 104 | +L0502,2,5,P,P,False,637.0,,9,0 | |
| 105 | +L0503,3,5,P,P,False,571.0,,54,0 | |
| 106 | +L0504,4,5,H1,H1,False,569.0,133.0,70,1 | |
| 107 | +L0505,5,5,H1,H1,False,661.0,113.0,50,1 | |
| 108 | +L0506,6,5,H1,H1,False,578.0,142.0,63,1 | |
| 109 | +L0507,7,5,H1,H1,False,619.0,115.0,26,1 | |
| 110 | +L0508,8,5,H1,H1,False,587.0,149.0,36,1 | |
| 111 | +L0509,9,5,H1,H1,False,607.0,143.0,17,1 | |
| 112 | +L0510,10,5,H1,H1,False,598.0,119.0,55,1 | |
| 113 | +L0511,11,5,H1,H1,False,614.0,133.0,10,1 | |
| 114 | +L0512,12,5,H1,H1,False,567.0,155.0,63,1 | |
| 115 | +L0513,13,5,H1,H1,False,609.0,130.0,65,1 | |
| 116 | +L0514,14,5,H1,H1,False,646.0,163.0,67,1 | |
| 117 | +L0515,15,5,H1,H1,False,569.0,148.0,25,1 | |
| 118 | +L0516,16,5,H1,H1,False,635.0,125.0,73,1 | |
| 119 | +L0517,17,5,H1,H1,False,574.0,128.0,58,1 | |
| 120 | +L0518,18,5,H1,H1,False,564.0,112.0,60,1 | |
| 121 | +L0519,19,5,H1,H1,False,627.0,131.0,20,1 | |
| 122 | +L0600,0,6,P,P,False,625.0,,36,0 | |
| 123 | +L0601,1,6,P,P,False,666.0,,52,0 | |
| 124 | +L0602,2,6,P,P,False,638.0,,26,0 | |
| 125 | +L0603,3,6,P,P,False,642.0,,40,0 | |
| 126 | +L0604,4,6,H1,H1,False,573.0,138.0,69,1 | |
| 127 | +L0605,5,6,H1,H1,False,562.0,121.0,66,1 | |
| 128 | +L0606,6,6,H1,H1,False,596.0,148.0,22,1 | |
| 129 | +L0607,7,6,H1,H1,False,556.0,125.0,59,1 | |
| 130 | +L0608,8,6,H1,H1,False,571.0,112.0,16,1 | |
| 131 | +L0609,9,6,H1,H1,False,629.0,105.0,25,1 | |
| 132 | +L0610,10,6,H1,H1,False,601.0,108.0,24,1 | |
| 133 | +L0611,11,6,H1,H1,False,558.0,140.0,30,1 | |
| 134 | +L0612,12,6,H1,H1,False,623.0,137.0,52,1 | |
| 135 | +L0613,13,6,H1,H1,False,616.0,98.0,28,1 | |
| 136 | +L0614,14,6,H1,H1,False,647.0,170.0,59,1 | |
| 137 | +L0615,15,6,H1,H1,False,619.0,154.0,28,1 | |
| 138 | +L0616,16,6,H1,H1,False,658.0,156.0,62,1 | |
| 139 | +L0617,17,6,H1,H1,False,670.0,207.0,67,1 | |
| 140 | +L0618,18,6,H1,H1,False,664.0,180.0,69,1 | |
| 141 | +L0619,19,6,H1,H1,False,607.0,174.0,63,1 | |
| 142 | +L0700,0,7,P,P,False,641.0,,21,0 | |
| 143 | +L0701,1,7,P,P,False,592.0,,14,0 | |
| 144 | +L0702,2,7,P,P,False,584.0,,48,0 | |
| 145 | +L0703,3,7,P,P,False,669.0,,19,0 | |
| 146 | +L0704,4,7,H1,H1,False,662.0,139.0,26,1 | |
| 147 | +L0705,5,7,H1,H1,False,608.0,109.0,54,1 | |
| 148 | +L0706,6,7,H1,H1,False,634.0,144.0,10,1 | |
| 149 | +L0707,7,7,H1,H1,False,588.0,162.0,41,1 | |
| 150 | +L0708,8,7,H1,H1,False,562.0,148.0,55,1 | |
| 151 | +L0709,9,7,H1,H1,False,556.0,157.0,68,1 | |
| 152 | +L0710,10,7,H1,H1,False,557.0,154.0,56,1 | |
| 153 | +L0711,11,7,H1,H1,False,582.0,144.0,44,1 | |
| 154 | +L0712,12,7,H1,H1,False,560.0,133.0,13,1 | |
| 155 | +L0713,13,7,H1,H1,False,638.0,163.0,48,1 | |
| 156 | +L0714,14,7,H1,H1,False,575.0,128.0,13,1 | |
| 157 | +L0715,15,7,H1,H1,False,565.0,169.0,31,1 | |
| 158 | +L0716,16,7,H1,H1,False,648.0,169.0,11,1 | |
| 159 | +L0717,17,7,H1,H1,False,629.0,144.0,55,1 | |
| 160 | +L0718,18,7,H1,H1,False,638.0,137.0,34,1 | |
| 161 | +L0719,19,7,H1,H1,False,653.0,114.0,15,1 | |
| 162 | +L0800,0,8,H1,H1,False,576.0,130.0,16,1 | |
| 163 | +L0801,1,8,H1,H1,False,640.0,115.0,47,1 | |
| 164 | +L0802,2,8,H1,H1,False,563.0,138.0,26,1 | |
| 165 | +L0803,3,8,H1,H1,False,622.0,128.0,44,1 | |
| 166 | +L0804,4,8,H1,H1,False,641.0,134.0,46,1 | |
| 167 | +L0805,5,8,H1,H1,False,573.0,149.0,10,1 | |
| 168 | +L0806,6,8,H1,H1,False,595.0,155.0,71,1 | |
| 169 | +L0807,7,8,H1,H2,True,567.0,121.0,40,1 | |
| 170 | +L0808,8,8,H1,H2,True,652.0,136.0,11,1 | |
| 171 | +L0809,9,8,H1,H2,True,671.0,146.0,49,1 | |
| 172 | +L0810,10,8,H2,H2,False,668.0,247.0,55,2 | |
| 173 | +L0811,11,8,H2,H2,False,743.0,220.0,26,4 | |
| 174 | +L0812,12,8,H2,H2,False,756.0,243.0,8,3 | |
| 175 | +L0813,13,8,H2,H2,False,799.0,199.0,59,4 | |
| 176 | +L0814,14,8,H2,H2,False,795.0,217.0,51,4 | |
| 177 | +L0815,15,8,H3,H3,False,1051.0,412.0,46,8 | |
| 178 | +L0816,16,8,H3,H3,False,1075.0,457.0,58,4 | |
| 179 | +L0817,17,8,H3,H3,False,1116.0,667.0,57,4 | |
| 180 | +L0818,18,8,H3,H3,False,1062.0,366.0,60,7 | |
| 181 | +L0819,19,8,H3,H3,False,1059.0,484.0,12,8 | |
| 182 | +L0900,0,9,H1,H1,False,557.0,122.0,19,1 | |
| 183 | +L0901,1,9,H1,H1,False,643.0,161.0,14,1 | |
| 184 | +L0902,2,9,H1,H1,False,597.0,119.0,54,1 | |
| 185 | +L0903,3,9,H1,H1,False,641.0,124.0,27,1 | |
| 186 | +L0904,4,9,H1,H1,False,629.0,137.0,52,1 | |
| 187 | +L0905,5,9,H1,H1,False,615.0,117.0,67,1 | |
| 188 | +L0906,6,9,H1,H1,False,603.0,148.0,10,1 | |
| 189 | +L0907,7,9,H1,H2,True,657.0,154.0,34,1 | |
| 190 | +L0908,8,9,H1,H2,True,552.0,184.0,50,1 | |
| 191 | +L0909,9,9,H1,H2,True,565.0,160.0,35,1 | |
| 192 | +L0910,10,9,H2,H2,False,673.0,255.0,41,4 | |
| 193 | +L0911,11,9,H2,H2,False,737.0,223.0,24,2 | |
| 194 | +L0912,12,9,H2,H2,False,734.0,249.0,49,3 | |
| 195 | +L0913,13,9,H2,H2,False,722.0,212.0,11,3 | |
| 196 | +L0914,14,9,H2,H2,False,795.0,237.0,11,2 | |
| 197 | +L0915,15,9,H3,H3,False,1167.0,556.0,20,4 | |
| 198 | +L0916,16,9,H3,H3,False,1136.0,548.0,51,7 | |
| 199 | +L0917,17,9,H3,H3,False,1095.0,504.0,26,4 | |
| 200 | +L0918,18,9,H3,H3,False,1187.0,578.0,47,5 | |
| 201 | +L0919,19,9,H3,H3,False,1037.0,483.0,13,4 | |
| 202 | +L1000,0,10,H1,H1,False,564.0,142.0,49,1 | |
| 203 | +L1001,1,10,H1,H1,False,613.0,128.0,24,1 | |
| 204 | +L1002,2,10,H1,H1,False,577.0,152.0,12,1 | |
| 205 | +L1003,3,10,H1,H1,False,567.0,128.0,24,1 | |
| 206 | +L1004,4,10,H1,H1,False,599.0,103.0,72,1 | |
| 207 | +L1005,5,10,H1,H1,False,628.0,139.0,19,1 | |
| 208 | +L1006,6,10,H1,H1,False,667.0,127.0,31,1 | |
| 209 | +L1007,7,10,H1,H2,True,575.0,140.0,23,1 | |
| 210 | +L1008,8,10,H1,H2,True,582.0,166.0,30,1 | |
| 211 | +L1009,9,10,H1,H2,True,595.0,149.0,40,1 | |
| 212 | +L1010,10,10,H2,H2,False,737.0,213.0,69,3 | |
| 213 | +L1011,11,10,H2,H2,False,708.0,247.0,69,2 | |
| 214 | +L1012,12,10,H2,H2,False,745.0,182.0,6,3 | |
| 215 | +L1013,13,10,H2,H2,False,767.0,176.0,50,3 | |
| 216 | +L1014,14,10,H2,H2,False,739.0,240.0,56,4 | |
| 217 | +L1015,15,10,H3,H3,False,1103.0,511.0,30,4 | |
| 218 | +L1016,16,10,H3,H3,False,1077.0,574.0,58,7 | |
| 219 | +L1017,17,10,H3,H3,False,1091.0,546.0,11,8 | |
| 220 | +L1018,18,10,H3,H3,False,1161.0,490.0,55,4 | |
| 221 | +L1019,19,10,H3,H3,False,1054.0,570.0,42,4 | |
| 222 | +L1100,0,11,H1,H1,False,651.0,106.0,70,1 | |
| 223 | +L1101,1,11,H1,H1,False,628.0,104.0,59,1 | |
| 224 | +L1102,2,11,H1,H1,False,581.0,147.0,36,1 | |
| 225 | +L1103,3,11,H1,H1,False,566.0,156.0,68,1 | |
| 226 | +L1104,4,11,H1,H1,False,589.0,150.0,40,1 | |
| 227 | +L1105,5,11,H1,H1,False,603.0,147.0,22,1 | |
| 228 | +L1106,6,11,H1,H1,False,606.0,106.0,62,1 | |
| 229 | +L1107,7,11,H1,H2,True,632.0,123.0,31,1 | |
| 230 | +L1108,8,11,H1,H2,True,669.0,122.0,25,1 | |
| 231 | +L1109,9,11,H1,H2,True,622.0,148.0,55,1 | |
| 232 | +L1110,10,11,H2,H2,False,771.0,202.0,67,4 | |
| 233 | +L1111,11,11,H2,H2,False,667.0,200.0,35,4 | |
| 234 | +L1112,12,11,H2,H2,False,721.0,189.0,56,3 | |
| 235 | +L1113,13,11,H2,H2,False,719.0,225.0,24,3 | |
| 236 | +L1114,14,11,H2,H2,False,729.0,181.0,6,2 | |
| 237 | +L1115,15,11,H3,H3,False,1202.0,484.0,21,8 | |
| 238 | +L1116,16,11,H3,H3,False,1041.0,662.0,43,6 | |
| 239 | +L1117,17,11,H3,H3,False,1087.0,491.0,55,4 | |
| 240 | +L1118,18,11,H3,H3,False,1131.0,637.0,29,6 | |
| 241 | +L1119,19,11,H3,H3,False,1173.0,393.0,16,7 | |
| 242 | +L1200,0,12,H1,H1,False,593.0,138.0,25,1 | |
| 243 | +L1201,1,12,H1,H1,False,620.0,145.0,49,1 | |
| 244 | +L1202,2,12,H1,H1,False,620.0,106.0,40,1 | |
| 245 | +L1203,3,12,H1,H1,False,597.0,162.0,64,1 | |
| 246 | +L1204,4,12,H1,H1,False,592.0,186.0,61,1 | |
| 247 | +L1205,5,12,H1,H1,False,650.0,134.0,27,1 | |
| 248 | +L1206,6,12,H1,H1,False,553.0,124.0,73,1 | |
| 249 | +L1207,7,12,H1,H2,True,554.0,102.0,42,1 | |
| 250 | +L1208,8,12,H1,H2,True,588.0,120.0,59,1 | |
| 251 | +L1209,9,12,H1,H2,True,625.0,165.0,31,1 | |
| 252 | +L1210,10,12,H2,H2,False,705.0,249.0,52,2 | |
| 253 | +L1211,11,12,H2,H2,False,768.0,262.0,21,4 | |
| 254 | +L1212,12,12,H2,H2,False,699.0,206.0,51,2 | |
| 255 | +L1213,13,12,H2,H2,False,784.0,232.0,63,2 | |
| 256 | +L1214,14,12,H2,H2,False,778.0,157.0,27,3 | |
| 257 | +L1215,15,12,H3,H3,False,1190.0,553.0,9,7 | |
| 258 | +L1216,16,12,H3,H3,False,1147.0,564.0,40,8 | |
| 259 | +L1217,17,12,H3,H3,False,1126.0,407.0,59,5 | |
| 260 | +L1218,18,12,H3,H3,False,1095.0,460.0,37,7 | |
| 261 | +L1219,19,12,H3,H3,False,1065.0,690.0,60,8 | |
| 262 | +L1300,0,13,H1,H1,False,652.0,124.0,71,1 | |
| 263 | +L1301,1,13,H1,H1,False,579.0,108.0,50,1 | |
| 264 | +L1302,2,13,H1,H1,False,556.0,88.0,19,1 | |
| 265 | +L1303,3,13,H1,H1,False,594.0,144.0,15,1 | |
| 266 | +L1304,4,13,H1,H1,False,634.0,138.0,38,1 | |
| 267 | +L1305,5,13,H1,H1,False,659.0,108.0,39,1 | |
| 268 | +L1306,6,13,H1,H1,False,560.0,152.0,32,1 | |
| 269 | +L1307,7,13,H1,H2,True,636.0,161.0,39,1 | |
| 270 | +L1308,8,13,H1,H2,True,555.0,125.0,18,1 | |
| 271 | +L1309,9,13,H1,H2,True,593.0,161.0,29,1 | |
| 272 | +L1310,10,13,H2,H2,False,681.0,204.0,70,3 | |
| 273 | +L1311,11,13,H2,H2,False,685.0,223.0,16,3 | |
| 274 | +L1312,12,13,H2,H2,False,686.0,267.0,38,3 | |
| 275 | +L1313,13,13,H2,H2,False,792.0,254.0,28,3 | |
| 276 | +L1314,14,13,H2,H2,False,700.0,185.0,55,3 | |
| 277 | +L1315,15,13,H3,H3,False,1177.0,446.0,52,5 | |
| 278 | +L1316,16,13,H3,H3,False,1182.0,509.0,24,6 | |
| 279 | +L1317,17,13,H3,H3,False,1083.0,629.0,43,7 | |
| 280 | +L1318,18,13,H3,H3,False,1035.0,565.0,10,8 | |
| 281 | +L1319,19,13,H3,H3,False,1031.0,668.0,53,8 | |
| 282 | +L1400,0,14,H1,H1,False,631.0,94.0,18,1 | |
| 283 | +L1401,1,14,H1,H1,False,603.0,85.0,25,1 | |
| 284 | +L1402,2,14,H1,H1,False,627.0,135.0,55,1 | |
| 285 | +L1403,3,14,H1,H1,False,644.0,174.0,55,1 | |
| 286 | +L1404,4,14,H1,H1,False,661.0,95.0,74,1 | |
| 287 | +L1405,5,14,H1,H1,False,640.0,116.0,55,1 | |
| 288 | +L1406,6,14,H1,H1,False,662.0,108.0,47,1 | |
| 289 | +L1407,7,14,H1,H2,True,613.0,137.0,51,1 | |
| 290 | +L1408,8,14,H1,H2,True,667.0,159.0,25,1 | |
| 291 | +L1409,9,14,H1,H2,True,671.0,118.0,55,1 | |
| 292 | +L1410,10,14,H2,H2,False,687.0,230.0,6,2 | |
| 293 | +L1411,11,14,H2,H2,False,713.0,205.0,35,3 | |
| 294 | +L1412,12,14,H2,H2,False,768.0,246.0,64,4 | |
| 295 | +L1413,13,14,H2,H2,False,797.0,233.0,49,2 | |
| 296 | +L1414,14,14,H2,H2,False,708.0,212.0,54,2 | |
| 297 | +L1415,15,14,H3,H3,False,1201.0,448.0,12,5 | |
| 298 | +L1416,16,14,H3,H3,False,1187.0,585.0,53,8 | |
| 299 | +L1417,17,14,H3,H3,False,1051.0,569.0,11,7 | |
| 300 | +L1418,18,14,H3,H3,False,1122.0,507.0,11,4 | |
| 301 | +L1419,19,14,H3,H3,False,1179.0,583.0,19,8 | |
| 302 | +L1500,0,15,H1,H1,False,573.0,106.0,44,1 | |
| 303 | +L1501,1,15,H1,H1,False,637.0,138.0,27,1 | |
| 304 | +L1502,2,15,H1,H1,False,638.0,147.0,36,1 | |
| 305 | +L1503,3,15,H1,H1,False,614.0,161.0,13,1 | |
| 306 | +L1504,4,15,H1,H1,False,654.0,136.0,48,1 | |
| 307 | +L1505,5,15,H1,H1,False,594.0,97.0,45,1 | |
| 308 | +L1506,6,15,H1,H1,False,600.0,154.0,52,1 | |
| 309 | +L1507,7,15,H1,H2,True,617.0,169.0,70,1 | |
| 310 | +L1508,8,15,H1,H2,True,659.0,181.0,10,1 | |
| 311 | +L1509,9,15,H1,H2,True,602.0,112.0,53,1 | |
| 312 | +L1510,10,15,H2,H2,False,729.0,265.0,16,2 | |
| 313 | +L1511,11,15,H2,H2,False,749.0,200.0,43,3 | |
| 314 | +L1512,12,15,H2,H2,False,777.0,284.0,29,4 | |
| 315 | +L1513,13,15,H2,H2,False,764.0,221.0,41,2 | |
| 316 | +L1514,14,15,H2,H2,False,686.0,307.0,54,2 | |
| 317 | +L1515,15,15,H3,H3,False,1075.0,625.0,5,7 | |
| 318 | +L1516,16,15,H3,H3,False,1161.0,503.0,40,7 | |
| 319 | +L1517,17,15,H3,H3,False,1055.0,577.0,17,6 | |
| 320 | +L1518,18,15,H3,H3,False,1100.0,525.0,60,5 | |
| 321 | +L1519,19,15,H3,H3,False,1034.0,512.0,51,4 | |
| 322 | +L1600,0,16,H1,H1,False,667.0,159.0,53,1 | |
| 323 | +L1601,1,16,H1,H1,False,563.0,142.0,30,1 | |
| 324 | +L1602,2,16,H1,H1,False,612.0,124.0,23,1 | |
| 325 | +L1603,3,16,H1,H1,False,651.0,158.0,41,1 | |
| 326 | +L1604,4,16,H1,H1,False,641.0,152.0,15,1 | |
| 327 | +L1605,5,16,H1,H1,False,616.0,139.0,49,1 | |
| 328 | +L1606,6,16,H1,H1,False,569.0,131.0,75,1 | |
| 329 | +L1607,7,16,H1,H2,True,562.0,129.0,45,1 | |
| 330 | +L1608,8,16,H1,H2,True,613.0,170.0,31,1 | |
| 331 | +L1609,9,16,H1,H2,True,624.0,120.0,41,1 | |
| 332 | +L1610,10,16,H2,H2,False,761.0,207.0,49,2 | |
| 333 | +L1611,11,16,H2,H2,False,772.0,233.0,12,4 | |
| 334 | +L1612,12,16,H2,H2,False,677.0,209.0,17,3 | |
| 335 | +L1613,13,16,H2,H2,False,682.0,181.0,47,4 | |
| 336 | +L1614,14,16,H2,H2,False,664.0,168.0,34,3 | |
| 337 | +L1615,15,16,H3,H3,False,1155.0,363.0,8,5 | |
| 338 | +L1616,16,16,H3,H3,False,1052.0,526.0,16,8 | |
| 339 | +L1617,17,16,H3,H3,False,1024.0,512.0,44,5 | |
| 340 | +L1618,18,16,H3,H3,False,1055.0,507.0,33,7 | |
| 341 | +L1619,19,16,H3,H3,False,1184.0,552.0,5,7 | |
| 342 | +L1700,0,17,H1,H1,False,606.0,152.0,11,1 | |
| 343 | +L1701,1,17,H1,H1,False,572.0,169.0,57,1 | |
| 344 | +L1702,2,17,H1,H1,False,653.0,185.0,57,1 | |
| 345 | +L1703,3,17,H1,H1,False,589.0,154.0,29,1 | |
| 346 | +L1704,4,17,H1,H1,False,647.0,123.0,38,1 | |
| 347 | +L1705,5,17,H1,H1,False,601.0,132.0,18,1 | |
| 348 | +L1706,6,17,H1,H1,False,572.0,168.0,15,1 | |
| 349 | +L1707,7,17,H1,H2,True,559.0,147.0,53,1 | |
| 350 | +L1708,8,17,H1,H2,True,587.0,132.0,51,1 | |
| 351 | +L1709,9,17,H1,H2,True,664.0,135.0,36,1 | |
| 352 | +L1710,10,17,H2,H2,False,798.0,236.0,28,2 | |
| 353 | +L1711,11,17,H2,H2,False,772.0,238.0,56,2 | |
| 354 | +L1712,12,17,H2,H2,False,744.0,221.0,10,4 | |
| 355 | +L1713,13,17,H2,H2,False,712.0,261.0,50,3 | |
| 356 | +L1714,14,17,H2,H2,False,754.0,245.0,50,3 | |
| 357 | +L1715,15,17,H3,H3,False,1130.0,473.0,50,8 | |
| 358 | +L1716,16,17,H3,H3,False,1111.0,567.0,36,4 | |
| 359 | +L1717,17,17,H3,H3,False,1120.0,575.0,26,7 | |
| 360 | +L1718,18,17,H3,H3,False,1094.0,614.0,30,8 | |
| 361 | +L1719,19,17,H3,H3,False,1017.0,577.0,41,5 | |
| 362 | +L1800,0,18,H1,H1,False,554.0,156.0,31,1 | |
| 363 | +L1801,1,18,H1,H1,False,620.0,136.0,37,1 | |
| 364 | +L1802,2,18,H1,H1,False,661.0,104.0,33,1 | |
| 365 | +L1803,3,18,H1,H1,False,641.0,133.0,56,1 | |
| 366 | +L1804,4,18,H1,H1,False,635.0,127.0,15,1 | |
| 367 | +L1805,5,18,H1,H1,False,620.0,150.0,71,1 | |
| 368 | +L1806,6,18,H1,H1,False,601.0,105.0,53,1 | |
| 369 | +L1807,7,18,H1,H2,True,597.0,127.0,56,1 | |
| 370 | +L1808,8,18,H1,H2,True,664.0,175.0,26,1 | |
| 371 | +L1809,9,18,H1,H2,True,660.0,159.0,39,1 | |
| 372 | +L1810,10,18,H2,H2,False,710.0,207.0,25,4 | |
| 373 | +L1811,11,18,H2,H2,False,719.0,246.0,35,4 | |
| 374 | +L1812,12,18,H2,H2,False,770.0,226.0,34,4 | |
| 375 | +L1813,13,18,H2,H2,False,782.0,253.0,27,2 | |
| 376 | +L1814,14,18,H2,H2,False,781.0,198.0,25,4 | |
| 377 | +L1815,15,18,H3,H3,False,1128.0,416.0,39,7 | |
| 378 | +L1816,16,18,H3,H3,False,1203.0,579.0,42,6 | |
| 379 | +L1817,17,18,H3,H3,False,1128.0,501.0,56,8 | |
| 380 | +L1818,18,18,H3,H3,False,1003.0,390.0,50,7 | |
| 381 | +L1819,19,18,H3,H3,False,1190.0,542.0,14,7 | |
| 382 | +L1900,0,19,H1,H1,False,599.0,148.0,30,1 | |
| 383 | +L1901,1,19,H1,H1,False,672.0,140.0,57,1 | |
| 384 | +L1902,2,19,H1,H1,False,648.0,150.0,57,1 | |
| 385 | +L1903,3,19,H1,H1,False,670.0,129.0,67,1 | |
| 386 | +L1904,4,19,H1,H1,False,663.0,152.0,54,1 | |
| 387 | +L1905,5,19,H1,H1,False,627.0,156.0,22,1 | |
| 388 | +L1906,6,19,H1,H1,False,623.0,107.0,55,1 | |
| 389 | +L1907,7,19,H1,H2,True,657.0,143.0,37,1 | |
| 390 | +L1908,8,19,H1,H2,True,641.0,127.0,56,1 | |
| 391 | +L1909,9,19,H1,H2,True,624.0,90.0,56,1 | |
| 392 | +L1910,10,19,H2,H2,False,686.0,182.0,49,4 | |
| 393 | +L1911,11,19,H2,H2,False,745.0,250.0,9,4 | |
| 394 | +L1912,12,19,H2,H2,False,695.0,213.0,36,3 | |
| 395 | +L1913,13,19,H2,H2,False,682.0,155.0,68,3 | |
| 396 | +L1914,14,19,H2,H2,False,687.0,184.0,21,3 | |
| 397 | +L1915,15,19,H3,H3,False,1207.0,564.0,16,8 | |
| 398 | +L1916,16,19,H3,H3,False,1140.0,483.0,5,6 | |
| 399 | +L1917,17,19,H3,H3,False,1130.0,604.0,39,8 | |
| 400 | +L1918,18,19,H3,H3,False,1056.0,555.0,32,5 | |
| 401 | +L1919,19,19,H3,H3,False,1089.0,403.0,12,4 | |
added
data/synthetic/ch08_zonage_ventes.csv
+483 −0
@@ -0,0 +1,483 @@ | ||
| 1 | +id_vente,id_lot,zone_initiale,rezone,date_vente,post_T0,traite_post,superficie_habitable_m2,superficie_terrain_m2,age_vente,prix_vente | |
| 2 | +V0001,L0304,H1,False,2023-03-31,False,False,167.0,614.0,33,386000.0 | |
| 3 | +V0002,L0305,H1,False,2025-03-22,True,False,160.0,571.0,72,362000.0 | |
| 4 | +V0003,L0305,H1,False,2025-03-25,True,False,160.0,571.0,72,361000.0 | |
| 5 | +V0004,L0306,H1,False,2024-09-26,False,False,147.0,600.0,25,387500.0 | |
| 6 | +V0005,L0307,H1,False,2026-02-26,True,False,145.0,562.0,39,346500.0 | |
| 7 | +V0006,L0308,H1,False,2025-12-31,True,False,192.0,645.0,64,406500.0 | |
| 8 | +V0007,L0309,H1,False,2025-07-19,True,False,147.0,611.0,62,355000.0 | |
| 9 | +V0008,L0309,H1,False,2026-03-17,True,False,147.0,611.0,63,370000.0 | |
| 10 | +V0009,L0310,H1,False,2025-09-15,True,False,169.0,629.0,56,408000.0 | |
| 11 | +V0010,L0310,H1,False,2026-07-02,True,False,169.0,629.0,57,409500.0 | |
| 12 | +V0011,L0311,H1,False,2024-05-28,False,False,150.0,647.0,39,385000.0 | |
| 13 | +V0012,L0311,H1,False,2024-07-04,False,False,150.0,647.0,39,374500.0 | |
| 14 | +V0013,L0312,H1,False,2025-03-11,True,False,117.0,574.0,38,311500.0 | |
| 15 | +V0014,L0312,H1,False,2026-05-24,True,False,117.0,574.0,39,361500.0 | |
| 16 | +V0015,L0313,H1,False,2025-09-25,True,False,150.0,638.0,55,383500.0 | |
| 17 | +V0016,L0314,H1,False,2023-01-12,False,False,119.0,663.0,25,363500.0 | |
| 18 | +V0017,L0314,H1,False,2024-03-09,False,False,119.0,663.0,26,373500.0 | |
| 19 | +V0018,L0315,H1,False,2024-01-10,False,False,101.0,650.0,71,242000.0 | |
| 20 | +V0019,L0315,H1,False,2026-04-22,True,False,101.0,650.0,73,322000.0 | |
| 21 | +V0020,L0316,H1,False,2025-05-21,True,False,115.0,629.0,51,354000.0 | |
| 22 | +V0021,L0317,H1,False,2025-10-18,True,False,117.0,588.0,70,310000.0 | |
| 23 | +V0022,L0318,H1,False,2026-08-23,True,False,121.0,567.0,49,370000.0 | |
| 24 | +V0023,L0319,H1,False,2023-01-27,False,False,102.0,636.0,12,356000.0 | |
| 25 | +V0024,L0404,H1,False,2025-02-23,True,False,118.0,552.0,42,308500.0 | |
| 26 | +V0025,L0405,H1,False,2023-11-29,False,False,164.0,559.0,13,491000.0 | |
| 27 | +V0026,L0405,H1,False,2023-12-21,False,False,164.0,559.0,13,394000.0 | |
| 28 | +V0027,L0406,H1,False,2023-09-08,False,False,152.0,591.0,22,405500.0 | |
| 29 | +V0028,L0406,H1,False,2024-11-19,False,False,152.0,591.0,23,398500.0 | |
| 30 | +V0029,L0407,H1,False,2024-05-07,False,False,103.0,597.0,12,344500.0 | |
| 31 | +V0030,L0408,H1,False,2025-06-03,True,False,207.0,601.0,23,479500.0 | |
| 32 | +V0031,L0408,H1,False,2025-11-23,True,False,207.0,601.0,23,472000.0 | |
| 33 | +V0032,L0409,H1,False,2024-04-01,False,False,151.0,576.0,48,382000.0 | |
| 34 | +V0033,L0409,H1,False,2025-01-29,True,False,151.0,576.0,49,369000.0 | |
| 35 | +V0034,L0410,H1,False,2025-08-22,True,False,177.0,554.0,46,389500.0 | |
| 36 | +V0035,L0411,H1,False,2023-08-14,False,False,181.0,559.0,41,416500.0 | |
| 37 | +V0036,L0411,H1,False,2025-07-29,True,False,181.0,559.0,43,429500.0 | |
| 38 | +V0037,L0412,H1,False,2025-03-04,True,False,130.0,617.0,12,393000.0 | |
| 39 | +V0038,L0412,H1,False,2026-08-26,True,False,130.0,617.0,13,417000.0 | |
| 40 | +V0039,L0413,H1,False,2025-01-11,True,False,135.0,628.0,13,403000.0 | |
| 41 | +V0040,L0413,H1,False,2025-07-18,True,False,135.0,628.0,13,401500.0 | |
| 42 | +V0041,L0414,H1,False,2024-12-02,False,False,124.0,670.0,34,367000.0 | |
| 43 | +V0042,L0415,H1,False,2024-08-13,False,False,134.0,562.0,41,389500.0 | |
| 44 | +V0043,L0416,H1,False,2023-11-15,False,False,113.0,582.0,27,356500.0 | |
| 45 | +V0044,L0416,H1,False,2025-08-22,True,False,113.0,582.0,29,375000.0 | |
| 46 | +V0045,L0417,H1,False,2023-09-02,False,False,115.0,599.0,30,348500.0 | |
| 47 | +V0046,L0417,H1,False,2025-07-20,True,False,115.0,599.0,32,349000.0 | |
| 48 | +V0047,L0418,H1,False,2024-08-21,False,False,146.0,626.0,50,389000.0 | |
| 49 | +V0048,L0418,H1,False,2025-10-13,True,False,146.0,626.0,51,371000.0 | |
| 50 | +V0049,L0419,H1,False,2023-06-07,False,False,114.0,649.0,32,355000.0 | |
| 51 | +V0050,L0419,H1,False,2025-05-07,True,False,114.0,649.0,34,372500.0 | |
| 52 | +V0051,L0504,H1,False,2024-12-18,False,False,133.0,569.0,68,368500.0 | |
| 53 | +V0052,L0504,H1,False,2025-05-06,True,False,133.0,569.0,69,332000.0 | |
| 54 | +V0053,L0505,H1,False,2024-04-12,False,False,113.0,661.0,48,307500.0 | |
| 55 | +V0054,L0506,H1,False,2023-09-16,False,False,142.0,578.0,60,321000.0 | |
| 56 | +V0055,L0507,H1,False,2025-01-19,True,False,115.0,619.0,25,353000.0 | |
| 57 | +V0056,L0508,H1,False,2023-03-06,False,False,149.0,587.0,33,369500.0 | |
| 58 | +V0057,L0509,H1,False,2023-01-29,False,False,143.0,607.0,14,387500.0 | |
| 59 | +V0058,L0509,H1,False,2026-03-30,True,False,143.0,607.0,17,412500.0 | |
| 60 | +V0059,L0510,H1,False,2023-12-07,False,False,119.0,598.0,52,334500.0 | |
| 61 | +V0060,L0511,H1,False,2025-04-29,True,False,133.0,614.0,9,435000.0 | |
| 62 | +V0061,L0511,H1,False,2026-04-16,True,False,133.0,614.0,10,454500.0 | |
| 63 | +V0062,L0512,H1,False,2023-08-16,False,False,155.0,567.0,60,312000.0 | |
| 64 | +V0063,L0512,H1,False,2023-08-29,False,False,155.0,567.0,60,347000.0 | |
| 65 | +V0064,L0513,H1,False,2023-03-11,False,False,130.0,609.0,62,304000.0 | |
| 66 | +V0065,L0513,H1,False,2024-10-25,False,False,130.0,609.0,63,326000.0 | |
| 67 | +V0066,L0514,H1,False,2023-12-18,False,False,163.0,646.0,64,324000.0 | |
| 68 | +V0067,L0514,H1,False,2025-01-16,True,False,163.0,646.0,66,390500.0 | |
| 69 | +V0068,L0515,H1,False,2023-09-07,False,False,148.0,569.0,22,394500.0 | |
| 70 | +V0069,L0516,H1,False,2025-01-28,True,False,125.0,635.0,72,301000.0 | |
| 71 | +V0070,L0517,H1,False,2024-12-03,False,False,128.0,574.0,56,330500.0 | |
| 72 | +V0071,L0518,H1,False,2026-01-04,True,False,112.0,564.0,60,287500.0 | |
| 73 | +V0072,L0518,H1,False,2026-02-18,True,False,112.0,564.0,60,373500.0 | |
| 74 | +V0073,L0519,H1,False,2025-02-07,True,False,131.0,627.0,19,422500.0 | |
| 75 | +V0074,L0604,H1,False,2023-11-03,False,False,138.0,573.0,66,318000.0 | |
| 76 | +V0075,L0604,H1,False,2025-08-13,True,False,138.0,573.0,68,396000.0 | |
| 77 | +V0076,L0605,H1,False,2024-10-07,False,False,121.0,562.0,64,315000.0 | |
| 78 | +V0077,L0606,H1,False,2023-04-21,False,False,148.0,596.0,19,372500.0 | |
| 79 | +V0078,L0607,H1,False,2025-11-13,True,False,125.0,556.0,58,315000.0 | |
| 80 | +V0079,L0608,H1,False,2024-09-03,False,False,112.0,571.0,14,361000.0 | |
| 81 | +V0080,L0608,H1,False,2024-09-18,False,False,112.0,571.0,14,307500.0 | |
| 82 | +V0081,L0609,H1,False,2025-04-04,True,False,105.0,629.0,24,340500.0 | |
| 83 | +V0082,L0610,H1,False,2023-08-17,False,False,108.0,601.0,21,336500.0 | |
| 84 | +V0083,L0610,H1,False,2024-12-16,False,False,108.0,601.0,22,348000.0 | |
| 85 | +V0084,L0611,H1,False,2024-08-22,False,False,140.0,558.0,28,380500.0 | |
| 86 | +V0085,L0611,H1,False,2026-06-10,True,False,140.0,558.0,30,426500.0 | |
| 87 | +V0086,L0612,H1,False,2025-05-23,True,False,137.0,623.0,51,411000.0 | |
| 88 | +V0087,L0613,H1,False,2023-05-27,False,False,98.0,616.0,25,335500.0 | |
| 89 | +V0088,L0613,H1,False,2023-11-23,False,False,98.0,616.0,25,301000.0 | |
| 90 | +V0089,L0614,H1,False,2023-09-28,False,False,170.0,647.0,56,378500.0 | |
| 91 | +V0090,L0615,H1,False,2023-06-03,False,False,154.0,619.0,25,419000.0 | |
| 92 | +V0091,L0615,H1,False,2023-11-25,False,False,154.0,619.0,25,410500.0 | |
| 93 | +V0092,L0616,H1,False,2024-07-28,False,False,156.0,658.0,60,397500.0 | |
| 94 | +V0093,L0617,H1,False,2024-12-21,False,False,207.0,670.0,65,430000.0 | |
| 95 | +V0094,L0618,H1,False,2023-11-14,False,False,180.0,664.0,66,360500.0 | |
| 96 | +V0095,L0619,H1,False,2023-11-10,False,False,174.0,607.0,60,363000.0 | |
| 97 | +V0096,L0619,H1,False,2025-11-06,True,False,174.0,607.0,62,336500.0 | |
| 98 | +V0097,L0704,H1,False,2023-09-25,False,False,139.0,662.0,23,363500.0 | |
| 99 | +V0098,L0704,H1,False,2024-03-31,False,False,139.0,662.0,24,408500.0 | |
| 100 | +V0099,L0705,H1,False,2025-11-12,True,False,109.0,608.0,53,309500.0 | |
| 101 | +V0100,L0706,H1,False,2024-01-09,False,False,144.0,634.0,8,433500.0 | |
| 102 | +V0101,L0707,H1,False,2023-03-13,False,False,162.0,588.0,38,414000.0 | |
| 103 | +V0102,L0707,H1,False,2024-05-23,False,False,162.0,588.0,39,432500.0 | |
| 104 | +V0103,L0708,H1,False,2023-07-19,False,False,148.0,562.0,52,366500.0 | |
| 105 | +V0104,L0708,H1,False,2024-12-03,False,False,148.0,562.0,53,358500.0 | |
| 106 | +V0105,L0709,H1,False,2023-12-01,False,False,157.0,556.0,65,351500.0 | |
| 107 | +V0106,L0710,H1,False,2025-09-04,True,False,154.0,557.0,55,327000.0 | |
| 108 | +V0107,L0710,H1,False,2025-11-14,True,False,154.0,557.0,55,394500.0 | |
| 109 | +V0108,L0711,H1,False,2024-02-22,False,False,144.0,582.0,42,373500.0 | |
| 110 | +V0109,L0711,H1,False,2024-08-09,False,False,144.0,582.0,42,346000.0 | |
| 111 | +V0110,L0712,H1,False,2025-03-21,True,False,133.0,560.0,12,446500.0 | |
| 112 | +V0111,L0713,H1,False,2023-07-13,False,False,163.0,638.0,45,374000.0 | |
| 113 | +V0112,L0714,H1,False,2023-03-17,False,False,128.0,575.0,10,422500.0 | |
| 114 | +V0113,L0715,H1,False,2024-07-22,False,False,169.0,565.0,29,400500.0 | |
| 115 | +V0114,L0716,H1,False,2026-02-03,True,False,169.0,648.0,11,500000.0 | |
| 116 | +V0115,L0717,H1,False,2026-01-03,True,False,144.0,629.0,55,372000.0 | |
| 117 | +V0116,L0718,H1,False,2024-01-18,False,False,137.0,638.0,32,385500.0 | |
| 118 | +V0117,L0718,H1,False,2026-02-24,True,False,137.0,638.0,34,455000.0 | |
| 119 | +V0118,L0719,H1,False,2025-11-08,True,False,114.0,653.0,14,427500.0 | |
| 120 | +V0119,L0800,H1,False,2024-10-12,False,False,130.0,576.0,14,368000.0 | |
| 121 | +V0120,L0800,H1,False,2025-02-18,True,False,130.0,576.0,15,393000.0 | |
| 122 | +V0121,L0801,H1,False,2025-01-06,True,False,115.0,640.0,46,357500.0 | |
| 123 | +V0122,L0801,H1,False,2025-09-20,True,False,115.0,640.0,46,371000.0 | |
| 124 | +V0123,L0802,H1,False,2024-05-18,False,False,138.0,563.0,24,376500.0 | |
| 125 | +V0124,L0802,H1,False,2026-02-03,True,False,138.0,563.0,26,435000.0 | |
| 126 | +V0125,L0803,H1,False,2023-12-06,False,False,128.0,622.0,41,376000.0 | |
| 127 | +V0126,L0803,H1,False,2024-08-23,False,False,128.0,622.0,42,383000.0 | |
| 128 | +V0127,L0804,H1,False,2024-09-20,False,False,134.0,641.0,44,364500.0 | |
| 129 | +V0128,L0804,H1,False,2024-10-25,False,False,134.0,641.0,44,390500.0 | |
| 130 | +V0129,L0805,H1,False,2023-01-04,False,False,149.0,573.0,7,418000.0 | |
| 131 | +V0130,L0805,H1,False,2023-06-12,False,False,149.0,573.0,7,406000.0 | |
| 132 | +V0131,L0806,H1,False,2026-04-21,True,False,155.0,595.0,71,371000.0 | |
| 133 | +V0132,L0807,H1,True,2026-04-25,True,True,121.0,567.0,40,380500.0 | |
| 134 | +V0133,L0808,H1,True,2025-09-07,True,True,136.0,652.0,10,429000.0 | |
| 135 | +V0134,L0809,H1,True,2024-02-16,False,False,146.0,671.0,47,405000.0 | |
| 136 | +V0135,L0809,H1,True,2024-10-01,False,False,146.0,671.0,47,431000.0 | |
| 137 | +V0136,L0810,H2,False,2025-09-28,True,False,247.0,668.0,54,524500.0 | |
| 138 | +V0137,L0811,H2,False,2026-06-22,True,False,220.0,743.0,26,688000.0 | |
| 139 | +V0138,L0812,H2,False,2023-06-26,False,False,243.0,756.0,5,595500.0 | |
| 140 | +V0139,L0813,H2,False,2026-01-22,True,False,199.0,799.0,59,569500.0 | |
| 141 | +V0140,L0813,H2,False,2026-05-20,True,False,199.0,799.0,59,508000.0 | |
| 142 | +V0141,L0814,H2,False,2023-06-21,False,False,217.0,795.0,48,546500.0 | |
| 143 | +V0142,L0814,H2,False,2024-06-20,False,False,217.0,795.0,49,586000.0 | |
| 144 | +V0143,L0815,H3,False,2024-09-30,False,False,412.0,1051.0,44,915500.0 | |
| 145 | +V0144,L0816,H3,False,2023-08-18,False,False,457.0,1075.0,55,855500.0 | |
| 146 | +V0145,L0817,H3,False,2024-06-21,False,False,667.0,1116.0,55,1125500.0 | |
| 147 | +V0146,L0818,H3,False,2026-07-22,True,False,366.0,1062.0,60,886000.0 | |
| 148 | +V0147,L0819,H3,False,2026-03-24,True,False,484.0,1059.0,12,1252000.0 | |
| 149 | +V0148,L0900,H1,False,2023-07-24,False,False,122.0,557.0,16,336000.0 | |
| 150 | +V0149,L0900,H1,False,2025-01-05,True,False,122.0,557.0,18,349000.0 | |
| 151 | +V0150,L0901,H1,False,2025-05-10,True,False,161.0,643.0,13,418500.0 | |
| 152 | +V0151,L0902,H1,False,2025-09-15,True,False,119.0,597.0,53,330000.0 | |
| 153 | +V0152,L0903,H1,False,2026-01-21,True,False,124.0,641.0,27,418500.0 | |
| 154 | +V0153,L0904,H1,False,2024-11-28,False,False,137.0,629.0,50,386000.0 | |
| 155 | +V0154,L0905,H1,False,2023-04-20,False,False,117.0,615.0,64,316000.0 | |
| 156 | +V0155,L0906,H1,False,2023-12-15,False,False,148.0,603.0,7,432500.0 | |
| 157 | +V0156,L0906,H1,False,2026-04-08,True,False,148.0,603.0,10,426000.0 | |
| 158 | +V0157,L0907,H1,True,2023-01-29,False,False,154.0,657.0,31,384500.0 | |
| 159 | +V0158,L0908,H1,True,2025-04-09,True,True,184.0,552.0,49,396500.0 | |
| 160 | +V0159,L0909,H1,True,2024-12-06,False,False,160.0,565.0,33,386500.0 | |
| 161 | +V0160,L0910,H2,False,2024-07-14,False,False,255.0,673.0,39,616500.0 | |
| 162 | +V0161,L0911,H2,False,2025-12-25,True,False,223.0,737.0,23,618000.0 | |
| 163 | +V0162,L0912,H2,False,2024-01-12,False,False,249.0,734.0,47,639500.0 | |
| 164 | +V0163,L0912,H2,False,2024-08-13,False,False,249.0,734.0,47,570500.0 | |
| 165 | +V0164,L0913,H2,False,2025-01-23,True,False,212.0,722.0,10,573500.0 | |
| 166 | +V0165,L0914,H2,False,2023-11-18,False,False,237.0,795.0,8,635000.0 | |
| 167 | +V0166,L0914,H2,False,2024-08-04,False,False,237.0,795.0,9,598000.0 | |
| 168 | +V0167,L0915,H3,False,2024-05-03,False,False,556.0,1167.0,18,1251000.0 | |
| 169 | +V0168,L0915,H3,False,2025-12-02,True,False,556.0,1167.0,19,1234500.0 | |
| 170 | +V0169,L0916,H3,False,2024-03-16,False,False,548.0,1136.0,49,1198000.0 | |
| 171 | +V0170,L0916,H3,False,2025-05-30,True,False,548.0,1136.0,50,1000500.0 | |
| 172 | +V0171,L0917,H3,False,2023-07-31,False,False,504.0,1095.0,23,1110500.0 | |
| 173 | +V0172,L0918,H3,False,2025-05-03,True,False,578.0,1187.0,46,1036000.0 | |
| 174 | +V0173,L0919,H3,False,2024-10-25,False,False,483.0,1037.0,11,998000.0 | |
| 175 | +V0174,L0919,H3,False,2026-08-21,True,False,483.0,1037.0,13,1211500.0 | |
| 176 | +V0175,L1000,H1,False,2024-02-08,False,False,142.0,564.0,47,372500.0 | |
| 177 | +V0176,L1000,H1,False,2026-08-27,True,False,142.0,564.0,49,373500.0 | |
| 178 | +V0177,L1001,H1,False,2024-01-26,False,False,128.0,613.0,22,348500.0 | |
| 179 | +V0178,L1002,H1,False,2025-02-20,True,False,152.0,577.0,11,443000.0 | |
| 180 | +V0179,L1003,H1,False,2025-09-13,True,False,128.0,567.0,23,382000.0 | |
| 181 | +V0180,L1003,H1,False,2026-07-15,True,False,128.0,567.0,24,407000.0 | |
| 182 | +V0181,L1004,H1,False,2024-02-23,False,False,103.0,599.0,70,278500.0 | |
| 183 | +V0182,L1004,H1,False,2025-12-08,True,False,103.0,599.0,71,281500.0 | |
| 184 | +V0183,L1005,H1,False,2024-09-26,False,False,139.0,628.0,17,434000.0 | |
| 185 | +V0184,L1005,H1,False,2026-01-14,True,False,139.0,628.0,19,462500.0 | |
| 186 | +V0185,L1006,H1,False,2024-11-11,False,False,127.0,667.0,29,379000.0 | |
| 187 | +V0186,L1007,H1,True,2025-06-22,True,True,140.0,575.0,22,410500.0 | |
| 188 | +V0187,L1008,H1,True,2025-07-01,True,True,166.0,582.0,29,481000.0 | |
| 189 | +V0188,L1009,H1,True,2025-03-21,True,True,149.0,595.0,39,403000.0 | |
| 190 | +V0189,L1010,H2,False,2025-07-28,True,False,213.0,737.0,68,506000.0 | |
| 191 | +V0190,L1011,H2,False,2023-01-10,False,False,247.0,708.0,66,438000.0 | |
| 192 | +V0191,L1011,H2,False,2025-08-24,True,False,247.0,708.0,68,563500.0 | |
| 193 | +V0192,L1012,H2,False,2025-07-02,True,False,182.0,745.0,5,597500.0 | |
| 194 | +V0193,L1012,H2,False,2026-07-17,True,False,182.0,745.0,6,620500.0 | |
| 195 | +V0194,L1013,H2,False,2023-09-13,False,False,176.0,767.0,47,418500.0 | |
| 196 | +V0195,L1013,H2,False,2024-08-28,False,False,176.0,767.0,48,464500.0 | |
| 197 | +V0196,L1014,H2,False,2023-04-09,False,False,240.0,739.0,53,534500.0 | |
| 198 | +V0197,L1014,H2,False,2026-08-24,True,False,240.0,739.0,56,617500.0 | |
| 199 | +V0198,L1015,H3,False,2025-07-06,True,False,511.0,1103.0,29,1275500.0 | |
| 200 | +V0199,L1015,H3,False,2025-10-06,True,False,511.0,1103.0,29,1252500.0 | |
| 201 | +V0200,L1016,H3,False,2024-03-15,False,False,574.0,1077.0,56,961500.0 | |
| 202 | +V0201,L1017,H3,False,2025-01-27,True,False,546.0,1091.0,10,1135000.0 | |
| 203 | +V0202,L1018,H3,False,2023-11-24,False,False,490.0,1161.0,52,1035500.0 | |
| 204 | +V0203,L1018,H3,False,2024-11-28,False,False,490.0,1161.0,53,1123000.0 | |
| 205 | +V0204,L1019,H3,False,2025-09-11,True,False,570.0,1054.0,41,1200500.0 | |
| 206 | +V0205,L1019,H3,False,2025-09-30,True,False,570.0,1054.0,41,1190000.0 | |
| 207 | +V0206,L1100,H1,False,2023-07-27,False,False,106.0,651.0,67,278000.0 | |
| 208 | +V0207,L1100,H1,False,2026-05-03,True,False,106.0,651.0,70,324500.0 | |
| 209 | +V0208,L1101,H1,False,2024-07-15,False,False,104.0,628.0,57,310500.0 | |
| 210 | +V0209,L1101,H1,False,2025-05-16,True,False,104.0,628.0,58,302000.0 | |
| 211 | +V0210,L1102,H1,False,2023-09-27,False,False,147.0,581.0,33,413000.0 | |
| 212 | +V0211,L1103,H1,False,2025-02-27,True,False,156.0,566.0,67,356500.0 | |
| 213 | +V0212,L1103,H1,False,2025-12-30,True,False,156.0,566.0,67,378500.0 | |
| 214 | +V0213,L1104,H1,False,2024-07-22,False,False,150.0,589.0,38,359000.0 | |
| 215 | +V0214,L1104,H1,False,2025-12-31,True,False,150.0,589.0,39,420500.0 | |
| 216 | +V0215,L1105,H1,False,2026-03-11,True,False,147.0,603.0,22,440000.0 | |
| 217 | +V0216,L1106,H1,False,2024-01-02,False,False,106.0,606.0,60,301000.0 | |
| 218 | +V0217,L1107,H1,True,2024-01-08,False,False,123.0,632.0,29,376500.0 | |
| 219 | +V0218,L1107,H1,True,2024-02-07,False,False,123.0,632.0,29,347500.0 | |
| 220 | +V0219,L1108,H1,True,2023-06-28,False,False,122.0,669.0,22,357500.0 | |
| 221 | +V0220,L1108,H1,True,2026-01-06,True,True,122.0,669.0,25,390000.0 | |
| 222 | +V0221,L1109,H1,True,2024-09-15,False,False,148.0,622.0,53,400000.0 | |
| 223 | +V0222,L1109,H1,True,2026-06-13,True,True,148.0,622.0,55,450500.0 | |
| 224 | +V0223,L1110,H2,False,2024-03-12,False,False,202.0,771.0,65,498000.0 | |
| 225 | +V0224,L1111,H2,False,2023-02-07,False,False,200.0,667.0,32,503500.0 | |
| 226 | +V0225,L1111,H2,False,2026-06-22,True,False,200.0,667.0,35,542000.0 | |
| 227 | +V0226,L1112,H2,False,2023-09-30,False,False,189.0,721.0,53,476000.0 | |
| 228 | +V0227,L1112,H2,False,2024-08-01,False,False,189.0,721.0,54,487000.0 | |
| 229 | +V0228,L1113,H2,False,2023-04-25,False,False,225.0,719.0,21,564500.0 | |
| 230 | +V0229,L1113,H2,False,2024-04-14,False,False,225.0,719.0,22,625000.0 | |
| 231 | +V0230,L1114,H2,False,2024-09-17,False,False,181.0,729.0,4,572000.0 | |
| 232 | +V0231,L1115,H3,False,2024-03-25,False,False,484.0,1202.0,19,1005500.0 | |
| 233 | +V0232,L1115,H3,False,2026-04-17,True,False,484.0,1202.0,21,1257000.0 | |
| 234 | +V0233,L1116,H3,False,2024-05-13,False,False,662.0,1041.0,41,1342000.0 | |
| 235 | +V0234,L1117,H3,False,2026-03-26,True,False,491.0,1087.0,55,1016500.0 | |
| 236 | +V0235,L1118,H3,False,2023-12-17,False,False,637.0,1131.0,26,1207500.0 | |
| 237 | +V0236,L1119,H3,False,2025-05-18,True,False,393.0,1173.0,15,1009500.0 | |
| 238 | +V0237,L1200,H1,False,2025-07-12,True,False,138.0,593.0,24,462000.0 | |
| 239 | +V0238,L1200,H1,False,2025-10-17,True,False,138.0,593.0,24,362500.0 | |
| 240 | +V0239,L1201,H1,False,2024-05-15,False,False,145.0,620.0,47,344500.0 | |
| 241 | +V0240,L1201,H1,False,2025-06-11,True,False,145.0,620.0,48,397500.0 | |
| 242 | +V0241,L1202,H1,False,2025-07-15,True,False,106.0,620.0,39,276500.0 | |
| 243 | +V0242,L1202,H1,False,2026-05-01,True,False,106.0,620.0,40,352500.0 | |
| 244 | +V0243,L1203,H1,False,2023-01-18,False,False,162.0,597.0,61,344500.0 | |
| 245 | +V0244,L1203,H1,False,2023-11-08,False,False,162.0,597.0,61,355000.0 | |
| 246 | +V0245,L1204,H1,False,2026-01-30,True,False,186.0,592.0,61,405000.0 | |
| 247 | +V0246,L1205,H1,False,2024-10-19,False,False,134.0,650.0,25,420500.0 | |
| 248 | +V0247,L1205,H1,False,2025-08-04,True,False,134.0,650.0,26,352500.0 | |
| 249 | +V0248,L1206,H1,False,2023-02-06,False,False,124.0,553.0,70,300500.0 | |
| 250 | +V0249,L1206,H1,False,2023-11-01,False,False,124.0,553.0,70,322000.0 | |
| 251 | +V0250,L1207,H1,True,2024-04-27,False,False,102.0,554.0,40,340000.0 | |
| 252 | +V0251,L1207,H1,True,2026-02-24,True,True,102.0,554.0,42,337500.0 | |
| 253 | +V0252,L1208,H1,True,2024-04-13,False,False,120.0,588.0,57,298500.0 | |
| 254 | +V0253,L1209,H1,True,2025-08-25,True,True,165.0,625.0,30,391500.0 | |
| 255 | +V0254,L1209,H1,True,2026-05-29,True,True,165.0,625.0,31,525500.0 | |
| 256 | +V0255,L1210,H2,False,2024-04-26,False,False,249.0,705.0,50,524000.0 | |
| 257 | +V0256,L1211,H2,False,2025-05-17,True,False,262.0,768.0,20,682500.0 | |
| 258 | +V0257,L1212,H2,False,2024-04-18,False,False,206.0,699.0,49,566000.0 | |
| 259 | +V0258,L1213,H2,False,2023-07-23,False,False,232.0,784.0,60,522500.0 | |
| 260 | +V0259,L1214,H2,False,2023-02-10,False,False,157.0,778.0,24,446000.0 | |
| 261 | +V0260,L1215,H3,False,2026-03-05,True,False,553.0,1190.0,9,1344500.0 | |
| 262 | +V0261,L1216,H3,False,2023-11-30,False,False,564.0,1147.0,37,1021000.0 | |
| 263 | +V0262,L1216,H3,False,2025-10-29,True,False,564.0,1147.0,39,1083000.0 | |
| 264 | +V0263,L1217,H3,False,2024-03-29,False,False,407.0,1126.0,57,811500.0 | |
| 265 | +V0264,L1217,H3,False,2026-05-08,True,False,407.0,1126.0,59,904000.0 | |
| 266 | +V0265,L1218,H3,False,2025-06-06,True,False,460.0,1095.0,36,954500.0 | |
| 267 | +V0266,L1218,H3,False,2025-12-18,True,False,460.0,1095.0,36,1185000.0 | |
| 268 | +V0267,L1219,H3,False,2023-12-07,False,False,690.0,1065.0,57,1162000.0 | |
| 269 | +V0268,L1219,H3,False,2025-01-15,True,False,690.0,1065.0,59,1170000.0 | |
| 270 | +V0269,L1300,H1,False,2025-12-01,True,False,124.0,652.0,70,348000.0 | |
| 271 | +V0270,L1300,H1,False,2026-06-04,True,False,124.0,652.0,71,401500.0 | |
| 272 | +V0271,L1301,H1,False,2024-04-20,False,False,108.0,579.0,48,283500.0 | |
| 273 | +V0272,L1301,H1,False,2024-05-01,False,False,108.0,579.0,48,367500.0 | |
| 274 | +V0273,L1302,H1,False,2024-06-20,False,False,88.0,556.0,17,323000.0 | |
| 275 | +V0274,L1303,H1,False,2023-11-03,False,False,144.0,594.0,12,425500.0 | |
| 276 | +V0275,L1304,H1,False,2023-03-14,False,False,138.0,634.0,35,325500.0 | |
| 277 | +V0276,L1304,H1,False,2023-08-07,False,False,138.0,634.0,35,354500.0 | |
| 278 | +V0277,L1305,H1,False,2023-01-24,False,False,108.0,659.0,36,311500.0 | |
| 279 | +V0278,L1305,H1,False,2023-02-09,False,False,108.0,659.0,36,351000.0 | |
| 280 | +V0279,L1306,H1,False,2023-01-09,False,False,152.0,560.0,29,325000.0 | |
| 281 | +V0280,L1306,H1,False,2023-01-24,False,False,152.0,560.0,29,404500.0 | |
| 282 | +V0281,L1307,H1,True,2025-01-18,True,True,161.0,636.0,38,449000.0 | |
| 283 | +V0282,L1307,H1,True,2025-10-24,True,True,161.0,636.0,38,471500.0 | |
| 284 | +V0283,L1308,H1,True,2023-02-19,False,False,125.0,555.0,15,333000.0 | |
| 285 | +V0284,L1308,H1,True,2024-10-13,False,False,125.0,555.0,16,369000.0 | |
| 286 | +V0285,L1309,H1,True,2024-07-01,False,False,161.0,593.0,27,389500.0 | |
| 287 | +V0286,L1309,H1,True,2026-03-09,True,True,161.0,593.0,29,478000.0 | |
| 288 | +V0287,L1310,H2,False,2024-07-06,False,False,204.0,681.0,68,499000.0 | |
| 289 | +V0288,L1311,H2,False,2023-05-29,False,False,223.0,685.0,13,626000.0 | |
| 290 | +V0289,L1311,H2,False,2025-10-09,True,False,223.0,685.0,15,585000.0 | |
| 291 | +V0290,L1312,H2,False,2024-09-01,False,False,267.0,686.0,36,600000.0 | |
| 292 | +V0291,L1312,H2,False,2026-01-27,True,False,267.0,686.0,38,681500.0 | |
| 293 | +V0292,L1313,H2,False,2025-07-11,True,False,254.0,792.0,27,658500.0 | |
| 294 | +V0293,L1313,H2,False,2026-03-06,True,False,254.0,792.0,28,717500.0 | |
| 295 | +V0294,L1314,H2,False,2025-04-05,True,False,185.0,700.0,54,419500.0 | |
| 296 | +V0295,L1315,H3,False,2025-06-11,True,False,446.0,1177.0,51,984000.0 | |
| 297 | +V0296,L1315,H3,False,2026-07-28,True,False,446.0,1177.0,52,1058000.0 | |
| 298 | +V0297,L1316,H3,False,2023-10-20,False,False,509.0,1182.0,21,1204500.0 | |
| 299 | +V0298,L1316,H3,False,2024-02-12,False,False,509.0,1182.0,22,1125500.0 | |
| 300 | +V0299,L1317,H3,False,2023-04-06,False,False,629.0,1083.0,40,1202500.0 | |
| 301 | +V0300,L1317,H3,False,2024-06-28,False,False,629.0,1083.0,41,1141500.0 | |
| 302 | +V0301,L1318,H3,False,2023-04-30,False,False,565.0,1035.0,7,1429000.0 | |
| 303 | +V0302,L1318,H3,False,2025-03-09,True,False,565.0,1035.0,9,1408500.0 | |
| 304 | +V0303,L1319,H3,False,2023-10-15,False,False,668.0,1031.0,50,1216000.0 | |
| 305 | +V0304,L1400,H1,False,2023-03-12,False,False,94.0,631.0,15,383000.0 | |
| 306 | +V0305,L1401,H1,False,2024-03-11,False,False,85.0,603.0,23,304000.0 | |
| 307 | +V0306,L1402,H1,False,2024-11-13,False,False,135.0,627.0,53,345500.0 | |
| 308 | +V0307,L1402,H1,False,2026-08-31,True,False,135.0,627.0,55,347000.0 | |
| 309 | +V0308,L1403,H1,False,2024-11-27,False,False,174.0,644.0,53,406500.0 | |
| 310 | +V0309,L1403,H1,False,2026-04-16,True,False,174.0,644.0,55,411500.0 | |
| 311 | +V0310,L1404,H1,False,2023-08-19,False,False,95.0,661.0,71,265000.0 | |
| 312 | +V0311,L1404,H1,False,2025-02-06,True,False,95.0,661.0,73,237000.0 | |
| 313 | +V0312,L1405,H1,False,2024-10-14,False,False,116.0,640.0,53,328500.0 | |
| 314 | +V0313,L1406,H1,False,2023-03-12,False,False,108.0,662.0,44,293000.0 | |
| 315 | +V0314,L1407,H1,True,2026-07-14,True,True,137.0,613.0,51,437000.0 | |
| 316 | +V0315,L1408,H1,True,2025-05-27,True,True,159.0,667.0,24,489500.0 | |
| 317 | +V0316,L1408,H1,True,2026-01-28,True,True,159.0,667.0,25,425500.0 | |
| 318 | +V0317,L1409,H1,True,2024-03-28,False,False,118.0,671.0,53,321000.0 | |
| 319 | +V0318,L1409,H1,True,2024-05-27,False,False,118.0,671.0,53,319500.0 | |
| 320 | +V0319,L1410,H2,False,2023-08-25,False,False,230.0,687.0,3,656500.0 | |
| 321 | +V0320,L1410,H2,False,2025-10-08,True,False,230.0,687.0,5,729000.0 | |
| 322 | +V0321,L1411,H2,False,2026-06-17,True,False,205.0,713.0,35,541500.0 | |
| 323 | +V0322,L1412,H2,False,2026-07-27,True,False,246.0,768.0,64,580000.0 | |
| 324 | +V0323,L1413,H2,False,2024-09-19,False,False,233.0,797.0,47,503000.0 | |
| 325 | +V0324,L1413,H2,False,2025-02-06,True,False,233.0,797.0,48,565000.0 | |
| 326 | +V0325,L1414,H2,False,2023-09-06,False,False,212.0,708.0,51,450000.0 | |
| 327 | +V0326,L1414,H2,False,2025-11-19,True,False,212.0,708.0,53,513000.0 | |
| 328 | +V0327,L1415,H3,False,2025-08-28,True,False,448.0,1201.0,11,1147000.0 | |
| 329 | +V0328,L1416,H3,False,2024-07-23,False,False,585.0,1187.0,51,1023000.0 | |
| 330 | +V0329,L1416,H3,False,2026-02-06,True,False,585.0,1187.0,53,1103000.0 | |
| 331 | +V0330,L1417,H3,False,2025-08-06,True,False,569.0,1051.0,10,1331000.0 | |
| 332 | +V0331,L1418,H3,False,2024-01-17,False,False,507.0,1122.0,9,1208500.0 | |
| 333 | +V0332,L1419,H3,False,2025-04-12,True,False,583.0,1179.0,18,1458000.0 | |
| 334 | +V0333,L1419,H3,False,2025-09-22,True,False,583.0,1179.0,18,1194500.0 | |
| 335 | +V0334,L1500,H1,False,2023-05-01,False,False,106.0,573.0,41,295500.0 | |
| 336 | +V0335,L1500,H1,False,2026-05-20,True,False,106.0,573.0,44,325000.0 | |
| 337 | +V0336,L1501,H1,False,2025-07-17,True,False,138.0,637.0,26,405500.0 | |
| 338 | +V0337,L1502,H1,False,2025-08-03,True,False,147.0,638.0,35,424000.0 | |
| 339 | +V0338,L1503,H1,False,2023-11-25,False,False,161.0,614.0,10,481500.0 | |
| 340 | +V0339,L1504,H1,False,2023-03-05,False,False,136.0,654.0,45,314000.0 | |
| 341 | +V0340,L1505,H1,False,2025-10-25,True,False,97.0,594.0,44,318500.0 | |
| 342 | +V0341,L1506,H1,False,2026-06-17,True,False,154.0,600.0,52,337000.0 | |
| 343 | +V0342,L1507,H1,True,2025-03-01,True,True,169.0,617.0,69,436000.0 | |
| 344 | +V0343,L1507,H1,True,2026-03-31,True,True,169.0,617.0,70,395500.0 | |
| 345 | +V0344,L1508,H1,True,2023-02-18,False,False,181.0,659.0,7,474000.0 | |
| 346 | +V0345,L1508,H1,True,2025-09-24,True,True,181.0,659.0,9,475000.0 | |
| 347 | +V0346,L1509,H1,True,2024-05-30,False,False,112.0,602.0,51,347500.0 | |
| 348 | +V0347,L1509,H1,True,2024-06-18,False,False,112.0,602.0,51,323000.0 | |
| 349 | +V0348,L1510,H2,False,2024-08-20,False,False,265.0,729.0,14,677000.0 | |
| 350 | +V0349,L1510,H2,False,2024-08-27,False,False,265.0,729.0,14,716000.0 | |
| 351 | +V0350,L1511,H2,False,2026-01-18,True,False,200.0,749.0,43,506500.0 | |
| 352 | +V0351,L1512,H2,False,2026-08-31,True,False,284.0,777.0,29,721500.0 | |
| 353 | +V0352,L1513,H2,False,2025-02-05,True,False,221.0,764.0,40,606000.0 | |
| 354 | +V0353,L1514,H2,False,2024-12-02,False,False,307.0,686.0,52,631000.0 | |
| 355 | +V0354,L1515,H3,False,2026-02-19,True,False,625.0,1075.0,5,1389000.0 | |
| 356 | +V0355,L1516,H3,False,2023-10-08,False,False,503.0,1161.0,37,1060500.0 | |
| 357 | +V0356,L1516,H3,False,2025-11-03,True,False,503.0,1161.0,39,1101000.0 | |
| 358 | +V0357,L1517,H3,False,2023-08-06,False,False,577.0,1055.0,14,1176000.0 | |
| 359 | +V0358,L1517,H3,False,2023-11-13,False,False,577.0,1055.0,14,1291000.0 | |
| 360 | +V0359,L1518,H3,False,2026-01-10,True,False,525.0,1100.0,60,1008500.0 | |
| 361 | +V0360,L1519,H3,False,2023-08-23,False,False,512.0,1034.0,48,1104000.0 | |
| 362 | +V0361,L1600,H1,False,2023-05-17,False,False,159.0,667.0,50,356000.0 | |
| 363 | +V0362,L1600,H1,False,2026-03-22,True,False,159.0,667.0,53,412500.0 | |
| 364 | +V0363,L1601,H1,False,2025-01-05,True,False,142.0,563.0,29,391500.0 | |
| 365 | +V0364,L1602,H1,False,2023-04-16,False,False,124.0,612.0,20,339500.0 | |
| 366 | +V0365,L1603,H1,False,2024-01-11,False,False,158.0,651.0,39,323500.0 | |
| 367 | +V0366,L1604,H1,False,2026-03-08,True,False,152.0,641.0,15,402500.0 | |
| 368 | +V0367,L1605,H1,False,2023-01-08,False,False,139.0,616.0,46,355500.0 | |
| 369 | +V0368,L1605,H1,False,2025-09-01,True,False,139.0,616.0,48,393500.0 | |
| 370 | +V0369,L1606,H1,False,2023-02-27,False,False,131.0,569.0,72,326000.0 | |
| 371 | +V0370,L1606,H1,False,2025-10-27,True,False,131.0,569.0,74,359000.0 | |
| 372 | +V0371,L1607,H1,True,2023-10-23,False,False,129.0,562.0,42,338500.0 | |
| 373 | +V0372,L1608,H1,True,2024-09-03,False,False,170.0,613.0,29,413000.0 | |
| 374 | +V0373,L1608,H1,True,2026-02-17,True,True,170.0,613.0,31,472500.0 | |
| 375 | +V0374,L1609,H1,True,2023-07-12,False,False,120.0,624.0,38,337500.0 | |
| 376 | +V0375,L1609,H1,True,2025-05-17,True,True,120.0,624.0,40,353500.0 | |
| 377 | +V0376,L1610,H2,False,2023-07-21,False,False,207.0,761.0,46,595000.0 | |
| 378 | +V0377,L1610,H2,False,2023-10-25,False,False,207.0,761.0,46,520500.0 | |
| 379 | +V0378,L1611,H2,False,2024-09-15,False,False,233.0,772.0,10,664000.0 | |
| 380 | +V0379,L1612,H2,False,2023-02-06,False,False,209.0,677.0,14,555000.0 | |
| 381 | +V0380,L1613,H2,False,2024-01-28,False,False,181.0,682.0,45,470000.0 | |
| 382 | +V0381,L1613,H2,False,2026-01-07,True,False,181.0,682.0,47,429000.0 | |
| 383 | +V0382,L1614,H2,False,2026-05-04,True,False,168.0,664.0,34,481000.0 | |
| 384 | +V0383,L1615,H3,False,2024-09-20,False,False,363.0,1155.0,6,1149000.0 | |
| 385 | +V0384,L1616,H3,False,2024-06-26,False,False,526.0,1052.0,14,1191000.0 | |
| 386 | +V0385,L1617,H3,False,2024-07-02,False,False,512.0,1024.0,42,1137000.0 | |
| 387 | +V0386,L1617,H3,False,2025-06-01,True,False,512.0,1024.0,43,1038000.0 | |
| 388 | +V0387,L1618,H3,False,2024-03-01,False,False,507.0,1055.0,31,1076500.0 | |
| 389 | +V0388,L1619,H3,False,2023-05-29,False,False,552.0,1184.0,2,1346500.0 | |
| 390 | +V0389,L1619,H3,False,2023-08-02,False,False,552.0,1184.0,2,1227000.0 | |
| 391 | +V0390,L1700,H1,False,2023-05-21,False,False,152.0,606.0,8,407000.0 | |
| 392 | +V0391,L1701,H1,False,2023-04-11,False,False,169.0,572.0,54,411000.0 | |
| 393 | +V0392,L1702,H1,False,2025-09-10,True,False,185.0,653.0,56,447500.0 | |
| 394 | +V0393,L1702,H1,False,2026-05-29,True,False,185.0,653.0,57,420500.0 | |
| 395 | +V0394,L1703,H1,False,2023-04-26,False,False,154.0,589.0,26,413000.0 | |
| 396 | +V0395,L1703,H1,False,2024-09-10,False,False,154.0,589.0,27,363000.0 | |
| 397 | +V0396,L1704,H1,False,2024-11-01,False,False,123.0,647.0,36,372500.0 | |
| 398 | +V0397,L1705,H1,False,2026-05-27,True,False,132.0,601.0,18,464500.0 | |
| 399 | +V0398,L1706,H1,False,2025-12-09,True,False,168.0,572.0,14,484000.0 | |
| 400 | +V0399,L1706,H1,False,2026-08-16,True,False,168.0,572.0,15,466000.0 | |
| 401 | +V0400,L1707,H1,True,2025-01-13,True,True,147.0,559.0,52,439000.0 | |
| 402 | +V0401,L1707,H1,True,2025-09-07,True,True,147.0,559.0,52,418500.0 | |
| 403 | +V0402,L1708,H1,True,2023-11-01,False,False,132.0,587.0,48,347000.0 | |
| 404 | +V0403,L1708,H1,True,2024-07-02,False,False,132.0,587.0,49,352000.0 | |
| 405 | +V0404,L1709,H1,True,2023-12-28,False,False,135.0,664.0,33,361000.0 | |
| 406 | +V0405,L1710,H2,False,2025-11-07,True,False,236.0,798.0,27,602000.0 | |
| 407 | +V0406,L1711,H2,False,2025-01-07,True,False,238.0,772.0,55,575000.0 | |
| 408 | +V0407,L1711,H2,False,2025-05-25,True,False,238.0,772.0,55,500500.0 | |
| 409 | +V0408,L1712,H2,False,2024-03-26,False,False,221.0,744.0,8,591500.0 | |
| 410 | +V0409,L1713,H2,False,2025-04-02,True,False,261.0,712.0,49,585000.0 | |
| 411 | +V0410,L1714,H2,False,2023-11-04,False,False,245.0,754.0,47,612500.0 | |
| 412 | +V0411,L1715,H3,False,2024-11-07,False,False,473.0,1130.0,48,1146000.0 | |
| 413 | +V0412,L1715,H3,False,2025-10-06,True,False,473.0,1130.0,49,1103000.0 | |
| 414 | +V0413,L1716,H3,False,2024-05-08,False,False,567.0,1111.0,34,1266500.0 | |
| 415 | +V0414,L1716,H3,False,2025-10-02,True,False,567.0,1111.0,35,1058000.0 | |
| 416 | +V0415,L1717,H3,False,2025-04-30,True,False,575.0,1120.0,25,1216000.0 | |
| 417 | +V0416,L1718,H3,False,2023-03-11,False,False,614.0,1094.0,27,1160500.0 | |
| 418 | +V0417,L1718,H3,False,2026-05-19,True,False,614.0,1094.0,30,1301500.0 | |
| 419 | +V0418,L1719,H3,False,2023-03-18,False,False,577.0,1017.0,38,1127000.0 | |
| 420 | +V0419,L1719,H3,False,2024-07-22,False,False,577.0,1017.0,39,1107500.0 | |
| 421 | +V0420,L1800,H1,False,2023-12-10,False,False,156.0,554.0,28,395500.0 | |
| 422 | +V0421,L1800,H1,False,2026-02-15,True,False,156.0,554.0,31,415500.0 | |
| 423 | +V0422,L1801,H1,False,2023-10-06,False,False,136.0,620.0,34,396000.0 | |
| 424 | +V0423,L1802,H1,False,2023-12-28,False,False,104.0,661.0,30,342000.0 | |
| 425 | +V0424,L1802,H1,False,2026-06-09,True,False,104.0,661.0,33,364500.0 | |
| 426 | +V0425,L1803,H1,False,2025-09-07,True,False,133.0,641.0,55,393500.0 | |
| 427 | +V0426,L1803,H1,False,2026-08-08,True,False,133.0,641.0,56,368000.0 | |
| 428 | +V0427,L1804,H1,False,2023-02-11,False,False,127.0,635.0,12,399000.0 | |
| 429 | +V0428,L1805,H1,False,2026-05-09,True,False,150.0,620.0,71,351000.0 | |
| 430 | +V0429,L1806,H1,False,2025-02-21,True,False,105.0,601.0,52,292500.0 | |
| 431 | +V0430,L1807,H1,True,2023-12-17,False,False,127.0,597.0,53,374000.0 | |
| 432 | +V0431,L1807,H1,True,2024-09-22,False,False,127.0,597.0,54,351000.0 | |
| 433 | +V0432,L1808,H1,True,2024-08-08,False,False,175.0,664.0,24,459000.0 | |
| 434 | +V0433,L1808,H1,True,2025-02-24,True,True,175.0,664.0,25,549000.0 | |
| 435 | +V0434,L1809,H1,True,2025-08-11,True,True,159.0,660.0,38,502500.0 | |
| 436 | +V0435,L1810,H2,False,2024-06-18,False,False,207.0,710.0,23,606500.0 | |
| 437 | +V0436,L1810,H2,False,2026-02-19,True,False,207.0,710.0,25,606000.0 | |
| 438 | +V0437,L1811,H2,False,2025-12-15,True,False,246.0,719.0,34,602500.0 | |
| 439 | +V0438,L1812,H2,False,2024-05-13,False,False,226.0,770.0,32,523500.0 | |
| 440 | +V0439,L1812,H2,False,2025-02-02,True,False,226.0,770.0,33,624000.0 | |
| 441 | +V0440,L1813,H2,False,2023-10-21,False,False,253.0,782.0,24,641000.0 | |
| 442 | +V0441,L1813,H2,False,2025-07-16,True,False,253.0,782.0,26,564000.0 | |
| 443 | +V0442,L1814,H2,False,2024-08-18,False,False,198.0,781.0,23,544000.0 | |
| 444 | +V0443,L1815,H3,False,2023-04-07,False,False,416.0,1128.0,36,949500.0 | |
| 445 | +V0444,L1816,H3,False,2023-10-25,False,False,579.0,1203.0,39,1132000.0 | |
| 446 | +V0445,L1816,H3,False,2026-07-31,True,False,579.0,1203.0,42,1121000.0 | |
| 447 | +V0446,L1817,H3,False,2024-07-02,False,False,501.0,1128.0,54,977500.0 | |
| 448 | +V0447,L1817,H3,False,2024-09-15,False,False,501.0,1128.0,54,1035500.0 | |
| 449 | +V0448,L1818,H3,False,2023-03-05,False,False,390.0,1003.0,47,785500.0 | |
| 450 | +V0449,L1818,H3,False,2025-10-15,True,False,390.0,1003.0,49,907500.0 | |
| 451 | +V0450,L1819,H3,False,2024-12-31,False,False,542.0,1190.0,12,1179000.0 | |
| 452 | +V0451,L1819,H3,False,2025-01-27,True,False,542.0,1190.0,13,1277500.0 | |
| 453 | +V0452,L1900,H1,False,2024-03-16,False,False,148.0,599.0,28,369500.0 | |
| 454 | +V0453,L1900,H1,False,2024-11-05,False,False,148.0,599.0,28,358000.0 | |
| 455 | +V0454,L1901,H1,False,2025-01-19,True,False,140.0,672.0,56,352500.0 | |
| 456 | +V0455,L1902,H1,False,2023-01-29,False,False,150.0,648.0,54,351500.0 | |
| 457 | +V0456,L1903,H1,False,2024-05-12,False,False,129.0,670.0,65,386000.0 | |
| 458 | +V0457,L1903,H1,False,2025-07-12,True,False,129.0,670.0,66,384500.0 | |
| 459 | +V0458,L1904,H1,False,2024-07-17,False,False,152.0,663.0,52,349500.0 | |
| 460 | +V0459,L1904,H1,False,2024-10-02,False,False,152.0,663.0,52,335000.0 | |
| 461 | +V0460,L1905,H1,False,2024-08-08,False,False,156.0,627.0,20,456000.0 | |
| 462 | +V0461,L1906,H1,False,2025-04-29,True,False,107.0,623.0,54,332000.0 | |
| 463 | +V0462,L1906,H1,False,2026-08-23,True,False,107.0,623.0,55,305500.0 | |
| 464 | +V0463,L1907,H1,True,2024-11-13,False,False,143.0,657.0,35,386000.0 | |
| 465 | +V0464,L1907,H1,True,2024-12-24,False,False,143.0,657.0,35,394500.0 | |
| 466 | +V0465,L1908,H1,True,2024-04-27,False,False,127.0,641.0,54,327000.0 | |
| 467 | +V0466,L1908,H1,True,2025-01-23,True,True,127.0,641.0,55,426000.0 | |
| 468 | +V0467,L1909,H1,True,2025-04-26,True,True,90.0,624.0,55,343000.0 | |
| 469 | +V0468,L1909,H1,True,2025-12-02,True,True,90.0,624.0,55,284000.0 | |
| 470 | +V0469,L1910,H2,False,2026-05-07,True,False,182.0,686.0,49,497500.0 | |
| 471 | +V0470,L1911,H2,False,2023-04-08,False,False,250.0,745.0,6,685500.0 | |
| 472 | +V0471,L1912,H2,False,2024-08-20,False,False,213.0,695.0,34,553500.0 | |
| 473 | +V0472,L1912,H2,False,2025-06-18,True,False,213.0,695.0,35,512000.0 | |
| 474 | +V0473,L1913,H2,False,2025-07-14,True,False,155.0,682.0,67,412000.0 | |
| 475 | +V0474,L1914,H2,False,2024-02-04,False,False,184.0,687.0,19,659500.0 | |
| 476 | +V0475,L1914,H2,False,2024-02-13,False,False,184.0,687.0,19,539000.0 | |
| 477 | +V0476,L1915,H3,False,2023-10-04,False,False,564.0,1207.0,13,1056000.0 | |
| 478 | +V0477,L1916,H3,False,2025-09-06,True,False,483.0,1140.0,4,1322000.0 | |
| 479 | +V0478,L1917,H3,False,2023-12-22,False,False,604.0,1130.0,36,1259500.0 | |
| 480 | +V0479,L1917,H3,False,2026-03-31,True,False,604.0,1130.0,39,1172000.0 | |
| 481 | +V0480,L1918,H3,False,2026-07-02,True,False,555.0,1056.0,32,1198500.0 | |
| 482 | +V0481,L1919,H3,False,2024-04-18,False,False,403.0,1089.0,10,1151500.0 | |
| 483 | +V0482,L1919,H3,False,2025-06-18,True,False,403.0,1089.0,11,1089500.0 | |
modified
manuscript/chapters/ch10.tex
+220 −1
@@ -1,2 +1,221 @@ | ||
| 1 | 1 | \chapter{Normes OEAQ, éthique et rapports}\label{ch:10} |
| 2 | −\stubchapter | |
| 2 | + | |
| 3 | +Les chapitres précédents ont établi ce qu'est la valeur, quels droits on évalue et dans quel cadre juridique et fiscal l'immeuble s'inscrit. Le présent chapitre traite d'une autre forme de contrainte, qui ne porte pas sur l'objet évalué mais sur celui qui évalue : les règles professionnelles qui encadrent l'acte d'évaluation au Québec, les devoirs déontologiques de l'évaluateur agréé, les cadres normatifs canadiens et internationaux avec lesquels la pratique québécoise dialogue, et la manière dont tout cela se traduit dans le rapport remis au client. Ces règles ne sont pas un ornement administratif : elles déterminent ce qu'un rapport doit contenir pour être recevable, ce qu'un évaluateur peut accepter comme mandat, ce qu'il doit divulguer et ce dont il répond. Le chapitre distingue soigneusement ce que la norme exige, ce que la loi prévoit, ce qui relève de l'usage et ce que l'on peut modéliser. Il comporte peu de calculs, mais il formalise quelques idées utiles : la conformité d'un rapport comme un taux de couverture d'une liste fermée d'éléments, l'incidence d'une hypothèse extraordinaire comme un écart de valeur, la probabilité qu'une lacune échappe à une chaîne de révisions. L'exemple intégral applique un contrôle automatique des douze éléments du contenu minimal à des extraits de rapports fictifs, et les exercices demandent au lecteur de réviser ces extraits d'un œil critique. | |
| 4 | + | |
| 5 | +\begin{objectifs} | |
| 6 | +\begin{itemize} | |
| 7 | +\item Décrire le système professionnel québécois de l'évaluation : titre réservé, champ d'exercice, conditions d'accès, obligations continues. | |
| 8 | +\item Distinguer, dans les Normes de pratique professionnelle de l'OEAQ, les règles coercitives des directives, la substance de l'évaluation (norme 1) de sa communication (norme 2), et les trois formes de rapport écrit. | |
| 9 | +\item Énumérer et appliquer les douze éléments du contenu minimal d'un rapport écrit, et formuler correctement une hypothèse extraordinaire avec son incidence sur la valeur. | |
| 10 | +\item Repérer les situations déontologiques sensibles (indépendance, conflit d'intérêts, double rôle, honoraires conditionnels, secret professionnel) et la conduite qu'exige le Code de déontologie. | |
| 11 | +\item Situer les NPP par rapport aux IVS 2025, au CUSPAP 2026, à l'USPAP 2024, au Red Book de la RICS et aux normes de l'IAAO, en connaissant l'état de vérification de chacun. | |
| 12 | +\item Organiser la révision et le contrôle de la qualité d'un rapport, et rédiger une conclusion de valeur dont le langage reflète fidèlement l'incertitude. | |
| 13 | +\item Expliquer le régime de responsabilité civile de l'évaluateur et le rôle de l'assurance responsabilité professionnelle. | |
| 14 | +\end{itemize} | |
| 15 | +\end{objectifs} | |
| 16 | + | |
| 17 | +\section{Le système professionnel québécois}\label{sec:10:systeme} | |
| 18 | + | |
| 19 | +\subsection{Un titre réservé, un champ d'exercice défini} | |
| 20 | + | |
| 21 | +Au Québec, l'évaluation immobilière est encadrée par le système professionnel que le Code des professions institue pour l'ensemble des ordres \parencite{codeProfessions}. L'Ordre des évaluateurs agréés du Québec (OEAQ), fondé en 1969, est l'un de ces ordres. La profession d'évaluateur est une profession à \motcle{titre réservé} et non à exercice exclusif : la loi réserve aux membres de l'Ordre les titres d'« évaluateur agréé » et d'« estimateur agréé », ainsi que les initiales « E.A. » et « C.App. » \parencite[art.~36~j)]{codeProfessions}, mais elle n'interdit pas à quiconque d'exprimer une opinion sur la valeur d'un immeuble. La conséquence pratique est importante : un courtier, un prêteur ou un logiciel peuvent produire une estimation, mais seul l'évaluateur agréé engage un titre professionnel, un code de déontologie, des normes de pratique, une inspection professionnelle et une assurance responsabilité obligatoire. C'est ce faisceau d'obligations, et non un monopole, qui fonde la valeur probante du rapport signé par un évaluateur agréé devant un tribunal, un prêteur ou une municipalité. | |
| 22 | + | |
| 23 | +Le champ d'exercice est décrit à l'article 37 j) du Code des professions : l'évaluateur agréé formule « une opinion dûment motivée de la valeur d'un bien ou d'un droit immobilier », et, en matière d'expropriation, de biens mobiliers ou immobiliers ; il détermine aussi la valeur des biens assujettis à l'évaluation foncière conformément à la Loi sur la fiscalité municipale \parencite[art.~37~j)]{codeProfessions}. Trois mots de cette définition structurent tout le chapitre. L'évaluateur produit une \emph{opinion}, c'est-à-dire un jugement professionnel et non un fait mesuré ; cette opinion est \emph{motivée}, c'est-à-dire qu'elle doit s'appuyer sur un raisonnement exposé et vérifiable ; elle porte sur la \emph{valeur} d'un bien ou d'un droit, ce qui renvoie aux bases de valeur du \cref{ch:2} et oblige à nommer celle qui est recherchée. L'évaluation foncière municipale, traitée au \cref{ch:9}, est expressément visée : c'est pourquoi les NPP comportent des normes propres au rôle d'évaluation, et pourquoi le Code de déontologie prévoit une règle particulière pour l'évaluateur qui confectionne un rôle et qui serait sollicité par un contribuable du même territoire. | |
| 24 | + | |
| 25 | +\begin{quebec} | |
| 26 | +L'OEAQ comptait \num{1188} membres au 31 mars 2025, dont 392 femmes et 796 hommes, auxquels s'ajoutaient 123 stagiaires et 535 étudiants inscrits ; 48 nouveaux permis avaient été délivrés durant l'exercice, et 7 radiations prononcées. Les membres exerçaient à 52,3 \% dans des firmes privées, à 19,7 \% dans des municipalités, à 18,1 \% dans les gouvernements et le secteur parapublic et à 3,2 \% dans des institutions financières ; les régions de Montréal (363 membres) et de la Capitale-Nationale (284) en regroupaient plus de la moitié \parencite[section~5, p.~59--61]{oeaqra2025}. Le rapport annuel 2024-2025 signale aussi que la couverture de l'assurance responsabilité professionnelle a été portée à \dollars{2000000} le 1\textsuperscript{er} avril 2025, qu'une modernisation des normes de pratique est en cours et qu'un projet de permis dédié aux évaluateurs résidentiels est à l'étude \parencite{oeaqra2025}. Ces chiffres, consultés le 31 octobre 2026, décrivent une profession de petite taille au regard des quelque \num{20000} ventes résidentielles trimestrielles du Québec, ce qui explique le recours croissant des prêteurs à des évaluations automatisées pour les dossiers simples et la place que l'inspection professionnelle accorde à la qualité des dossiers. | |
| 27 | +\end{quebec} | |
| 28 | + | |
| 29 | +\subsection{L'accès à la profession} | |
| 30 | + | |
| 31 | +Les conditions de délivrance du permis sont fixées par règlement de l'Ordre \parencite{permisoeaq}. Le candidat doit détenir un diplôme reconnu ou une équivalence, obtenir un certificat de stagiaire, accomplir un stage, suivre le cours de l'Ordre sur les normes et l'éthique, réussir l'examen d'admission et acquitter les frais, le tout dans un délai de cinq ans à compter du certificat de stagiaire \parencite[art.~1--2]{permisoeaq}. Le stage a une durée totale de 48 semaines, réparties en une ou plusieurs périodes d'au moins quatre semaines chacune, à raison d'au moins 35 heures par semaine \parencite[art.~8]{permisoeaq}. L'examen est annuel, comporte une partie orale et une partie écrite, porte sur le Code des professions, les règlements de l'Ordre, les normes de pratique, les méthodes et techniques d'évaluation, et exige la note minimale C \parencite[art.~23, 27, 29]{permisoeaq}. Le règlement situe l'examen « avant le premier dimanche de novembre », alors que le site de l'Ordre indique qu'il se tient au mois de septembre \parencite{oeaqdevenir} ; cette divergence entre le texte réglementaire et l'information administrative, relevée lors de la vérification des sources, est signalée ici parce qu'elle illustre une règle de méthode : lorsque deux sources officielles diffèrent, le texte réglementaire fait foi et l'écart est documenté. | |
| 32 | + | |
| 33 | +Trois diplômes universitaires québécois donnent accès au permis selon la page d'information de l'Ordre : le baccalauréat en administration des affaires de l'UQAM (concentration immobilier), le programme de gestion urbaine et immobilière de l'Université Laval et le programme de gestion immobilière et évaluation de l'UQO \parencite{oeaqdevenir}. Pour les diplômes obtenus hors du Québec, le règlement sur les normes d'équivalence exige un diplôme universitaire d'au moins \num{1350} heures, dont 585 heures réparties par domaine, et prévoit des équivalences de formation \parencite[art.~2, 4]{equivalenceoeaq}. Le \cref{tab:10:reglements} rassemble les règlements de l'Ordre que l'évaluateur rencontre dans sa pratique quotidienne, avec la disposition clé de chacun. | |
| 34 | + | |
| 35 | +\begin{table}[htbp] | |
| 36 | +\caption{Règlements de l'OEAQ encadrant la pratique (RLRQ, c. C-26) et dispositions clés}\label{tab:10:reglements} | |
| 37 | +\centering | |
| 38 | +\small | |
| 39 | +\begin{tabularx}{\textwidth}{lYY} | |
| 40 | +\toprule | |
| 41 | +Règlement & Objet & Disposition clé (paraphrase) \\ | |
| 42 | +\midrule | |
| 43 | +r.~123 Code de déontologie & Devoirs envers le public, le client et la profession & Normes de pratique obligatoires (art.~4) ; indépendance (art.~17) ; conflits d'intérêts (art.~19) ; honoraires conditionnels interdits sauf exceptions (art.~24.1) ; rapport conforme aux normes (art.~41) \\ | |
| 44 | +r.~124 Comité d'inspection professionnelle & Surveillance de l'exercice & Inspection portant « plus particulièrement sur les dossiers, livres et registres » ; comité de 12 membres inscrits depuis au moins 5 ans ; avis de 15 jours ; enquête particulière sur la compétence (art.~19) \\ | |
| 45 | +r.~126 Délivrance des permis & Accès à la profession & Diplôme, certificat de stagiaire, stage de 48 semaines, cours normes et éthique, examen annuel ; délai de 5 ans (art.~1--2, 8, 23) \\ | |
| 46 | +r.~127.1 Formation continue obligatoire & Maintien de la compétence & Au moins 30 heures par période de deux ans débutant le 1\textsuperscript{er} janvier d'une année paire, dont au moins 3 heures en éthique, déontologie et normes ; report maximal de 5 heures ; radiation en cas de défaut (art.~1--3, 17) \\ | |
| 47 | +r.~129 Équivalences & Diplômes et formation hors Québec & Diplôme d'au moins \num{1350} heures dont 585 réparties (art.~2) \\ | |
| 48 | +r.~133 Tenue des dossiers et cessation d'exercice & Dossier professionnel & Un dossier par contrat ou client ; contenu prescrit ; conservation d'au moins 5 ans après le dernier service ; confidentialité ; cession et garde provisoire (art.~2, 4, 6, 7, 17--29) \\ | |
| 49 | +\bottomrule | |
| 50 | +\end{tabularx} | |
| 51 | +\tabsource{synthèse de l'auteur d'après LégisQuébec, textes à jour au 1\textsuperscript{er} juillet 2026, consultés le 31 octobre 2026 \parencite{deontOEAQ,inspectionoeaq,permisoeaq,fcOEAQ,equivalenceoeaq,tenuedossiersoeaq} ; liste des règlements : \textcite{oeaqreglements}.} | |
| 52 | +\end{table} | |
| 53 | + | |
| 54 | +\subsection{Les obligations continues : formation, dossiers, inspection} | |
| 55 | + | |
| 56 | +L'inscription au tableau ne clôt pas les obligations de l'évaluateur ; elle en ouvre trois autres, permanentes. La première est la \motcle{formation continue}. Le règlement exige au moins 30 heures d'activités de formation continue par période de référence de deux ans, qui débute le 1\textsuperscript{er} janvier d'une année paire, dont au moins 3 heures en éthique, en déontologie et en normes de pratique \parencite[art.~1--2]{fcOEAQ}. Un excédent d'au plus 5 heures peut être reporté à la période suivante ; la déclaration doit être transmise au plus tard le 31 janvier qui suit la fin de la période, les pièces justificatives conservées cinq ans, et le défaut non corrigé entraîne la radiation \parencite[art.~3, 7, 8, 17]{fcOEAQ}. On peut écrire le mécanisme du report sous forme récursive : si $H_t$ désigne les heures accomplies durant la période $t$ et $r_t$ le report reçu de la période précédente, le report transmis à la période suivante est | |
| 57 | +\begin{equation}\label{eq:10:report} | |
| 58 | +r_{t+1} = \min\bigl\{5,\ \max\{0,\ H_t + \min\{5, r_t\} - 30\}\bigr\}, | |
| 59 | +\end{equation} | |
| 60 | +et la période est conforme si $H_t + \min\{5, r_t\} \geq 30$ et si les heures d'éthique, de déontologie et de normes atteignent 3. Le report ne s'applique qu'au volume total ; le minimum de trois heures en éthique doit être satisfait à l'intérieur de chaque période. L'\cref{ex:10:2} applique cette règle. | |
| 61 | + | |
| 62 | +La deuxième obligation est la \motcle{tenue des dossiers}. L'évaluateur constitue un dossier par contrat de services ou par client, y consigne les éléments prescrits et le conserve pendant au moins cinq ans après le dernier service rendu \parencite[art.~2, 4, 6]{tenuedossiersoeaq}. Cette durée a une portée qui dépasse l'archivage : c'est le dossier, et non le rapport, qui permet de reconstituer après coup le raisonnement, les données consultées, les vérifications faites et les communications avec le client. Un rapport sans dossier est une conclusion sans preuve. La troisième obligation est de se soumettre à l'\motcle{inspection professionnelle}. Le comité d'inspection, formé de douze membres inscrits au tableau depuis au moins cinq ans, surveille l'exercice de la profession « plus particulièrement sur les dossiers, livres et registres », donne un avis d'au moins quinze jours et peut recommander une enquête particulière sur la compétence d'un membre \parencite[art.~1, 2, 11, 19]{inspectionoeaq}. Les trois obligations se tiennent : la formation entretient la compétence, le dossier en garde la trace, l'inspection la vérifie. Le \cref{sec:10:revision} montre comment le contrôle interne de la qualité prolonge cette logique à l'échelle de chaque rapport. | |
| 63 | + | |
| 64 | +\section{Les Normes de pratique professionnelle}\label{sec:10:npp} | |
| 65 | + | |
| 66 | +\subsection{Architecture des normes} | |
| 67 | + | |
| 68 | +Les \motcle{Normes de pratique professionnelle} (NPP) de l'OEAQ sont le texte de référence de la pratique québécoise. L'édition en vigueur est celle « en vigueur depuis le 1\textsuperscript{er} février 2024 », date de la révision de la norme 11 consécutive à la nouvelle Loi concernant l'expropriation ; le fichier diffusé par l'Ordre est daté du 24 mars 2025, et les textes de fond des normes 1 et 2 portent des mentions d'adoption de 1998, 2000 et 2016 \parencite{oeaqNormes,oeaqnpppage}. Les normes sont organisées par paires : à chaque type d'acte professionnel correspond une norme sur l'acte lui-même et une norme sur sa communication. Le \cref{tab:10:structure} en donne la structure. Les normes 1 et 2, qui régissent l'évaluation d'un bien immobilier et la communication de ses résultats, sont celles que le présent ouvrage mobilise ; les normes 19 à 22 concernent l'évaluation municipale et sont évoquées au \cref{ch:9}. Les normes 17 et 18 sont annoncées « à venir ». Il n'existe pas, contrairement à une idée répandue, de « norme d'inspection professionnelle » dans les NPP : l'inspection professionnelle relève d'un règlement distinct \parencite{inspectionoeaq}, et l'inspection de l'immeuble, elle, est une règle de la norme 2. | |
| 69 | + | |
| 70 | +\begin{table}[htbp] | |
| 71 | +\caption{Structure des Normes de pratique professionnelle de l'OEAQ (édition en vigueur depuis le 1\textsuperscript{er} février 2024)}\label{tab:10:structure} | |
| 72 | +\centering | |
| 73 | +\small | |
| 74 | +\begin{tabular}{llp{62mm}} | |
| 75 | +\toprule | |
| 76 | +Normes & Objet & Remarques \\ | |
| 77 | +\midrule | |
| 78 | +1 et 2 & Acte d'évaluation d'un bien immobilier ; communication & Cœur de la pratique ; règle 1.2 (12 éléments de substance), règle 2.3 (12 éléments du rapport), 2.4 inspection, 2.5 signature \\ | |
| 79 | +3 et 4 & Examen d'un rapport d'évaluation & Révision par un tiers ; voir \cref{sec:10:revision} \\ | |
| 80 | +5 et 6 & Consultation & Services autres que l'opinion de valeur \\ | |
| 81 | +7 et 8 & Biens meubles & Hors du périmètre de l'ouvrage \\ | |
| 82 | +9 et 10 & Entreprises & Hors du périmètre de l'ouvrage \\ | |
| 83 | +11 et 12 ; 11.1 et 12.1 & Expropriation & Norme 11 révisée le 1\textsuperscript{er} février 2024 \\ | |
| 84 | +13 et 14 & Assurance & Valeur de reconstruction (voir \cref{ch:7}) \\ | |
| 85 | +15 et 16 & Fonds de prévoyance & Copropriété divise (\cref{ch:7,ch:42}) \\ | |
| 86 | +17 et 18 & --- & « À venir » dans l'édition consultée \\ | |
| 87 | +19 ; 20 à 20.3 & Rôle d'évaluation foncière et locative ; documents, évaluation municipale, révision & Voir \cref{ch:9} \\ | |
| 88 | +21 et 22 & Recours & Contestation et expertise devant le TAQ \\ | |
| 89 | +\bottomrule | |
| 90 | +\end{tabular} | |
| 91 | +\tabsource{table des matières des NPP \parencite{oeaqNormes}, consultée le 31 octobre 2026 ; annexes A à F non reproduites.} | |
| 92 | +\end{table} | |
| 93 | + | |
| 94 | +\subsection{Règles coercitives et directives} | |
| 95 | + | |
| 96 | +La première distinction à maîtriser est celle que l'introduction des NPP établit entre deux catégories de dispositions : « Les normes de pratique sont constituées de règles coercitives et de directives » \parencite[Intro-1]{oeaqNormes}. Les \motcle{règles coercitives} s'imposent sans exception ; l'introduction en dresse la liste, qui comprend notamment la règle 1.1, l'élément 12 de la règle 1.2 et les règles 2.1 à 2.5. Les \motcle{directives} décrivent la pratique attendue, mais l'évaluateur peut s'en écarter avec l'accord préalable du client, à condition d'exposer l'écart dans son rapport, ce qu'exige l'élément 11 de la règle 2.3 \parencite[Intro-1 ; règle 2.3]{oeaqNormes}. La distinction n'est pas académique. Elle signifie qu'un rapport ne peut jamais omettre l'un des douze éléments du contenu minimal ni se dispenser d'inspection sans explication, mais qu'il peut, par exemple, s'écarter d'une manière habituelle de présenter une analyse si le client y consent et si le rapport le dit. Elle signifie aussi que la conformité se vérifie à deux niveaux : la présence des éléments obligatoires, qui est binaire, et la justification des écarts aux directives, qui relève du jugement. Dans tout ce chapitre, la formule « la norme exige » renvoie à une règle coercitive, et « la norme prévoit » ou « il est d'usage » à une directive ou à une pratique. | |
| 97 | + | |
| 98 | +\subsection{Norme 1 : la substance de l'évaluation} | |
| 99 | + | |
| 100 | +La norme 1 porte sur l'acte d'évaluation lui-même, indépendamment de la façon dont il sera communiqué. Sa règle 1.2 énumère douze éléments que tout acte d'évaluation immobilière doit traiter \parencite[règle 1.2]{oeaqNormes} : l'objet de l'évaluation ; sa fin ; son but, c'est-à-dire la valeur recherchée ; sa date ; l'étendue du travail ; les réserves et hypothèses extraordinaires ; les biens meubles compris ; les démembrements du droit de propriété ; les restrictions au droit de propriété ; les forces du marché ; l'usage le meilleur et le plus profitable ; les méthodes et techniques. Trois de ces éléments méritent un commentaire immédiat, parce qu'ils relient la norme aux chapitres qui précèdent. L'élément 3 dispose que « Dans tous les cas, l'évaluateur doit définir le but qu'il vise à atteindre, i.e. la valeur qu'il recherche » \parencite[règle 1.2, élément 3]{oeaqNormes} : c'est l'obligation de nommer la base de valeur, et de retenir la définition légale lorsque la loi en prescrit une, comme la valeur réelle de la Loi sur la fiscalité municipale \parencite[art.~43--45]{lfm} ou l'indemnité d'expropriation. Les éléments 8 et 9 imposent d'identifier les démembrements et les restrictions au droit de propriété, ce qui est l'objet des \cref{ch:2,ch:6,ch:7}. L'élément 6 exige que l'évaluateur tienne compte « de toute réserve et hypothèse extraordinaires pouvant avoir une influence directe sur l'évaluation de l'immeuble » \parencite[règle 1.2, élément 6]{oeaqNormes} ; la \cref{sec:10:hypotheses} y revient. | |
| 101 | + | |
| 102 | +La norme 1 décrit donc un \emph{processus} autant qu'un contenu. La \cref{fig:10:processus} le schématise en sept étapes, de la définition du mandat à la communication, et indique pour chacune les éléments de la règle 1.2 qu'elle traite et les éléments de la règle 2.3 auxquels elle donnera lieu dans le rapport. L'ordre des étapes est celui de la pratique nord-américaine, que l'on retrouve chez \textcite{appraisal2026} et dans l'édition canadienne \parencite{appraisalCdn} ; il n'est pas prescrit tel quel par les NPP, mais il épouse leur logique : on ne peut pas choisir une méthode avant d'avoir identifié les droits évalués et l'usage le meilleur et le plus profitable, ni formuler une conclusion avant d'avoir réconcilié les indications. | |
| 103 | + | |
| 104 | +\begin{figure}[htbp] | |
| 105 | +\centering | |
| 106 | +\begin{tikzpicture}[node distance=3.2mm and 6mm, every node/.style={font=\sffamily\footnotesize}, | |
| 107 | + etape/.style={draw=brandPrimary, fill=brandLight, rounded corners=1pt, minimum width=46mm, minimum height=9mm, align=center}, | |
| 108 | + sub/.style={draw=brandGrayLight, fill=white, rounded corners=1pt, minimum width=36mm, minimum height=9mm, align=center, text width=34mm, font=\sffamily\scriptsize}, | |
| 109 | + rap/.style={draw=brandGrayLight, fill=white, rounded corners=1pt, minimum width=36mm, minimum height=9mm, align=center, text width=34mm, font=\sffamily\scriptsize}, | |
| 110 | + fleche/.style={-{Stealth[length=2mm]}, brandGray, thick}, lien/.style={brandGrayLight, thin, dashed}] | |
| 111 | +\node[etape] (e1) {1. Définir le mandat\\ objet, client, fin, but}; | |
| 112 | +\node[etape, below=of e1] (e2) {2. Identifier le bien et les droits\\ désignation, démembrements, restrictions}; | |
| 113 | +\node[etape, below=of e2] (e3) {3. Fixer la date et l'étendue du travail\\ hypothèses, réserves, inspection}; | |
| 114 | +\node[etape, below=of e3] (e4) {4. Analyser le marché\\ forces du marché, données}; | |
| 115 | +\node[etape, below=of e4] (e5) {5. Conclure à l'usage le meilleur\\ et le plus profitable}; | |
| 116 | +\node[etape, below=of e5] (e6) {6. Appliquer et réconcilier les méthodes\\ comparaison, coût, revenu}; | |
| 117 | +\node[etape, below=of e6, fill=brandSecondary!30] (e7) {7. Communiquer\\ rapport écrit, attestation, signature}; | |
| 118 | +\foreach \i/\j in {e1/e2, e2/e3, e3/e4, e4/e5, e5/e6, e6/e7} \draw[fleche] (\i) -- (\j); | |
| 119 | +\node[sub, left=of e1] (s1) {Règle 1.2 : él. 1 objet, 2 fin, 3 but}; | |
| 120 | +\node[sub, left=of e2] (s2) {Règle 1.2 : él. 7 biens meubles, 8 démembrements, 9 restrictions}; | |
| 121 | +\node[sub, left=of e3] (s3) {Règle 1.2 : él. 4 date, 5 étendue, 6 réserves et hypothèses extraordinaires}; | |
| 122 | +\node[sub, left=of e4] (s4) {Règle 1.2 : él. 10 forces du marché}; | |
| 123 | +\node[sub, left=of e5] (s5) {Règle 1.2 : él. 11 usage le meilleur et le plus profitable}; | |
| 124 | +\node[sub, left=of e6] (s6) {Règle 1.2 : él. 12 méthodes et techniques (coercitif)}; | |
| 125 | +\node[rap, right=of e1] (r1) {Règle 2.3 : él. 3 but et fin, 4 définition de la valeur}; | |
| 126 | +\node[rap, right=of e2] (r2) {Règle 2.3 : él. 1 identification, 2 droits évalués}; | |
| 127 | +\node[rap, right=of e3] (r3) {Règle 2.3 : él. 5 date, 6 étendue, 7 réserves et hypothèses ; règle 2.4 inspection}; | |
| 128 | +\node[rap, right=of e4] (r4) {Règle 2.3 : él. 8 traitement de l'information}; | |
| 129 | +\node[rap, right=of e5] (r5) {Règle 2.3 : él. 9 usage le meilleur et le plus profitable}; | |
| 130 | +\node[rap, right=of e6] (r6) {Règle 2.3 : él. 8 raisonnement, 10 rejet d'une méthode, 11 conformité et écarts}; | |
| 131 | +\node[rap, right=of e7] (r7) {Règle 2.3 : él. 12 attestation ; règle 2.5 signature ; règle 2.1 qualités du rapport}; | |
| 132 | +\foreach \i/\j in {s1/e1, s2/e2, s3/e3, s4/e4, s5/e5, s6/e6} \draw[lien] (\i) -- (\j); | |
| 133 | +\foreach \i/\j in {e1/r1, e2/r2, e3/r3, e4/r4, e5/r5, e6/r6, e7/r7} \draw[lien] (\i) -- (\j); | |
| 134 | +\node[above=1mm of s1, text=brandGray] {Substance (norme 1)}; | |
| 135 | +\node[above=1mm of e1, text=brandGray] {Processus d'évaluation}; | |
| 136 | +\node[above=1mm of r1, text=brandGray] {Communication (norme 2)}; | |
| 137 | +\end{tikzpicture} | |
| 138 | +\caption{Le processus d'évaluation en sept étapes, mis en correspondance avec les douze éléments de substance de la règle 1.2 (à gauche) et les douze éléments du rapport écrit de la règle 2.3 (à droite). Chaque étape alimente une ou plusieurs rubriques du rapport ; aucune rubrique ne peut être rédigée sans l'étape qui la fonde.}\label{fig:10:processus} | |
| 139 | +\figsource{schéma de l'auteur d'après la structure des NPP \parencite{oeaqNormes} ; l'ordre des étapes suit l'usage nord-américain \parencite{appraisal2026}.} | |
| 140 | +\end{figure} | |
| 141 | + | |
| 142 | +\subsection{Norme 2 : la communication} | |
| 143 | + | |
| 144 | +La norme 2 régit la communication des résultats. Sa règle 2.1 fixe trois qualités que tout rapport doit posséder : un exposé clair et précis ; une information suffisante pour que le destinataire comprenne le rapport et puisse s'y fier ; la divulgation claire de « toute hypothèse ou toute réserve extraordinaire pouvant avoir une influence directe sur l'évaluation », avec l'indication de son incidence sur la valeur \parencite[règle 2.1 a), b), c)]{oeaqNormes}. Les règles 2.2 et 2.3 distinguent ensuite trois formes de rapport écrit, que le \cref{tab:10:formes} résume : le rapport narratif complet, le rapport abrégé et le rapport de mise à jour. La différence entre les formes ne tient pas à la liste des éléments traités, qui est la même, mais à la profondeur du traitement, que les NPP expriment par trois verbes : \emph{décrire}, \emph{résumer}, \emph{mentionner}. Le rapport de mise à jour est réservé au même client et renvoie au rapport original \parencite[règles 2.2--2.3 et annexe B]{oeaqNormes}. On retiendra que la forme abrégée n'autorise jamais l'omission : un élément résumé en une phrase reste un élément présent, et un élément absent rend le rapport non conforme, quelle que soit sa forme. | |
| 145 | + | |
| 146 | +\begin{table}[htbp] | |
| 147 | +\caption{Les trois formes de rapport écrit selon la norme 2 des NPP}\label{tab:10:formes} | |
| 148 | +\centering | |
| 149 | +\small | |
| 150 | +\begin{tabularx}{\textwidth}{lYYl} | |
| 151 | +\toprule | |
| 152 | +Forme & Usage typique & Traitement des douze éléments & Verbe \\ | |
| 153 | +\midrule | |
| 154 | +Rapport narratif complet & Litiges, expropriation, immeubles complexes, mandats institutionnels & Chaque élément est développé, les données et le raisonnement sont exposés en détail & Décrire \\ | |
| 155 | +Rapport abrégé & Financement hypothécaire résidentiel, mandats courants, formulaires normalisés & Chaque élément est présent mais condensé ; les données détaillées demeurent au dossier & Résumer \\ | |
| 156 | +Rapport de mise à jour & Même client, même immeuble, nouvelle date d'évaluation & Renvoi au rapport original ; chaque élément est au moins mentionné ; les changements sont décrits & Mentionner \\ | |
| 157 | +\bottomrule | |
| 158 | +\end{tabularx} | |
| 159 | +\tabsource{paraphrase de l'auteur d'après les règles 2.2 et 2.3 et l'annexe B des NPP \parencite{oeaqNormes} ; l'annexe B n'est pas reproduite.} | |
| 160 | +\end{table} | |
| 161 | + | |
| 162 | +Le cœur de la norme 2 est la règle 2.3, règle coercitive, qui énonce que tous les rapports écrits, de quelque forme qu'ils soient, doivent inclure douze éléments \parencite[règle 2.3]{oeaqNormes}. Le \cref{tab:10:elements} les met en correspondance avec les douze éléments de substance de la règle 1.2. Les deux listes ne coïncident pas terme à terme, et la différence est instructive. La règle 1.2 décrit ce que l'évaluateur doit \emph{faire} ; la règle 2.3 décrit ce que le lecteur doit \emph{trouver}. Ainsi, les forces du marché et les méthodes de la règle 1.2 se retrouvent dans l'élément 8 de la règle 2.3, « traitement de l'information et raisonnement », qui exige que le lecteur puisse suivre le chemin des données à la conclusion ; les biens meubles, les démembrements et les restrictions se retrouvent dans l'identification et les droits évalués ; et trois éléments de la règle 2.3 n'ont pas d'équivalent direct dans la règle 1.2, parce qu'ils sont propres à la communication : l'explication du rejet d'une méthode traditionnelle (élément 10), la déclaration de conformité à la substance et l'exposé des écarts aux directives (élément 11), et l'attestation (élément 12). | |
| 163 | + | |
| 164 | +\begin{table}[htbp] | |
| 165 | +\caption{Grille de contenu du rapport écrit : les douze éléments de la règle 2.3 et leur correspondance avec la règle 1.2}\label{tab:10:elements} | |
| 166 | +\centering | |
| 167 | +\small | |
| 168 | +\begin{tabularx}{\textwidth}{clYl} | |
| 169 | +\toprule | |
| 170 | +{N\textsuperscript{o}} & Élément du rapport (règle 2.3) & Ce que le lecteur doit pouvoir vérifier & Règle 1.2 \\ | |
| 171 | +\midrule | |
| 172 | +1 & Identification physique et légale & Quel immeuble, quel lot, quelle adresse, quelles composantes ; biens meubles inclus & 1, 7 \\ | |
| 173 | +2 & Droits de propriété évalués & Pleine propriété ou droit démembré ; charges, servitudes, baux, restrictions & 8, 9 \\ | |
| 174 | +3 & But et fin de l'évaluation & Quelle valeur est recherchée, pour quel usage, pour quel destinataire & 2, 3 \\ | |
| 175 | +4 & Définition de la valeur & Texte de la définition retenue (valeur marchande des NPP, valeur réelle de la LFM, etc.) & 3 \\ | |
| 176 | +5 & Date d'évaluation & Date à laquelle l'opinion s'applique, distincte de la date du rapport et de l'inspection & 4 \\ | |
| 177 | +6 & Étendue du travail & Recherches, vérifications, inspection, limites du travail accompli & 5 \\ | |
| 178 | +7 & Réserves et hypothèses de base & Réserves limitant la portée ; hypothèses de base ; hypothèses extraordinaires avec incidence & 6 \\ | |
| 179 | +8 & Traitement de l'information et raisonnement & Données, analyse du marché, méthodes, ajustements, réconciliation & 10, 12 \\ | |
| 180 | +9 & Usage le meilleur et le plus profitable & Conclusion motivée sur l'usage, terrain vacant et tel qu'aménagé & 11 \\ | |
| 181 | +10 & Rejet d'une méthode traditionnelle & Pourquoi une méthode n'a pas été appliquée & 12 \\ | |
| 182 | +11 & Conformité à la substance ; écarts aux directives & Déclaration de conformité à la norme 1 ; écarts convenus avec le client & --- \\ | |
| 183 | +12 & Attestation & Déclaration signée et datée selon le modèle imposé & --- \\ | |
| 184 | +\bottomrule | |
| 185 | +\end{tabularx} | |
| 186 | +\tabsource{paraphrase de l'auteur d'après les règles 1.2 et 2.3 des NPP \parencite{oeaqNormes} ; la correspondance (dernière colonne) est une interprétation de l'auteur, non un tableau de la norme.} | |
| 187 | +\end{table} | |
| 188 | + | |
| 189 | +Cette liste fermée permet de définir une mesure élémentaire de conformité. Pour un rapport donné, soit $c_k$ l'indicatrice de présence de l'élément $k$ : | |
| 190 | +\begin{equation}\label{eq:10:indicatrice} | |
| 191 | +c_k = \mathbf{1}\{\text{l'élément } k \text{ de la règle 2.3 est traité dans le rapport}\}, \qquad k = 1, \dots, 12 . | |
| 192 | +\end{equation} | |
| 193 | +Le \motcle{taux de conformité formelle} est la proportion des éléments présents : | |
| 194 | +\begin{equation}\label{eq:10:taux} | |
| 195 | +\tau = \frac{1}{12} \sum_{k=1}^{12} c_k . | |
| 196 | +\end{equation} | |
| 197 | +Puisque la règle 2.3 est coercitive, le seul taux acceptable est $\tau = 1$ ; tout rapport pour lequel $\tau < 1$ est non conforme, et la liste des $k$ tels que $c_k = 0$ est la liste des corrections à apporter. La mesure est volontairement rudimentaire : elle ne dit rien de la qualité du traitement, et un élément présent peut être mal traité. Son intérêt est opérationnel : elle se prête à une liste de contrôle, à une révision par un tiers et, comme le montre l'exemple de la \cref{sec:10:exemple}, à un contrôle automatique de premier niveau. | |
| 198 | + | |
| 199 | +\subsection{Hypothèses extraordinaires et réserves}\label{sec:10:hypotheses} | |
| 200 | + | |
| 201 | +Parmi les douze éléments, le septième concentre la plus grande part des difficultés rédactionnelles et des litiges. La règle 2.3 dispose que « Dans tous les rapports, l'évaluateur doit mentionner les réserves limitant la portée de son acte et les hypothèses de base », et que les hypothèses extraordinaires doivent être décrites et rappelées chaque fois que la conclusion de valeur est énoncée \parencite[règle 2.3, élément 7]{oeaqNormes}. L'annexe E des NPP fournit un modèle de réserves et d'hypothèses de base, non contraignant quant à la forme mais à suivre sur le fond \parencite{oeaqNormes}. Il faut distinguer trois notions. Une \motcle{réserve} (ou condition limitative) délimite ce que l'évaluateur n'a pas fait ou ne garantit pas : il n'a pas vérifié les titres, il n'est pas ingénieur, il n'a pas inspecté les parties inaccessibles. Une \motcle{hypothèse de base} est une supposition courante, tenue pour vraie sauf indication contraire : le titre est valide, les renseignements fournis sont exacts, aucun vice caché n'affecte le bien. Une \motcle{hypothèse extraordinaire} est une supposition propre au mandat, dont la fausseté changerait matériellement la conclusion : le permis sera délivré, la contamination signalée est absente, l'immeuble sera livré conforme aux plans. | |
| 202 | + | |
| 203 | +Le CUSPAP 2026 de l'Institut canadien des évaluateurs (ICE, \emph{Appraisal Institute of Canada}) offre une terminologie plus fine, utile pour clarifier la pratique québécoise. Il distingue l'\emph{extraordinary assumption}, hypothèse directement liée au mandat qui, si elle se révélait fausse, « could materially alter the opinions or conclusions » ; la \emph{hypothetical condition}, cas particulier où l'on tient pour vraie une information « simulated but untrue » sur les caractéristiques physiques, légales ou économiques du bien ; et l'\emph{extraordinary limiting condition}, modification ou exclusion d'une règle de la norme qui peut réduire la fiabilité du rapport \parencite[s.~3, Definitions]{cuspap2026}. La condition hypothétique est l'hypothèse extraordinaire dont on \emph{sait} qu'elle est fausse à la date d'évaluation : évaluer un immeuble « comme si » les travaux étaient achevés, par exemple pour un financement à la construction. Les NPP ne distinguent pas formellement ces deux cas, mais l'exigence d'en indiquer l'incidence sur la valeur s'applique aux deux, et la pratique prudente consiste à nommer explicitement la nature de l'hypothèse. | |
| 204 | + | |
| 205 | +L'exigence d'indiquer l'incidence sur la valeur se formalise simplement. Soit $V_{\mid H}$ la valeur conclue sous l'hypothèse extraordinaire $H$ et $V_{\mid \neg H}$ la valeur qui prévaudrait si $H$ était fausse. L'\motcle{incidence} de l'hypothèse est l'écart | |
| 206 | +\begin{equation}\label{eq:10:incidence} | |
| 207 | +\Delta V_H = V_{\mid H} - V_{\mid \neg H}, | |
| 208 | +\end{equation} | |
| 209 | +que le rapport exprime en dollars et, utilement, en proportion de $V_{\mid \neg H}$. La raison pour laquelle la norme exige cette indication apparaît si l'on considère le lecteur du rapport, qui ne sait pas si $H$ est vraie. S'il attribue à $H$ une probabilité $p$, la valeur qu'il devrait retenir pour sa décision est l'espérance | |
| 210 | +\begin{equation}\label{eq:10:esperance} | |
| 211 | +\E[V] = p\, V_{\mid H} + (1 - p)\, V_{\mid \neg H} = V_{\mid \neg H} + p\, \Delta V_H , | |
| 212 | +\end{equation} | |
| 213 | +de sorte qu'un rapport qui ne donne que $V_{\mid H}$ surestime la valeur attendue de $(1 - p)\,\Delta V_H$. Donner $\Delta V_H$, ou les deux valeurs, restitue au lecteur l'information nécessaire ; c'est lui, et non l'évaluateur, qui connaît ou apprécie $p$. L'évaluateur ne doit pas remplacer $V_{\mid H}$ par $\E[V]$, parce que la valeur marchande est une estimation conditionnelle à un état du bien et non un pari sur cet état ; mais il doit rendre le calcul possible. Dans l'extrait fictif B de la \cref{sec:10:exemple}, l'hypothèse qu'un permis d'agrandissement a été obtenu conduit à une valeur de \dollars{612000} ; si, sans permis, la valeur était de \dollars{575000}, l'incidence serait de \dollars{37000}, soit 6,43 \%, et un lecteur qui attribuerait 60 \% de chances à l'existence du permis retiendrait \dollars{597200}. Ces chiffres sont fictifs et ne servent qu'à illustrer le mécanisme. | |
| 214 | + | |
| 215 | +\begin{pratique} | |
| 216 | +Une hypothèse extraordinaire bien rédigée comporte quatre éléments : l'énoncé précis de ce qui est supposé ; la raison pour laquelle l'évaluateur ne peut pas le vérifier à la date du rapport ; l'incidence sur la valeur si l'hypothèse se révélait fausse, en dollars ou en pourcentage, ou au minimum son sens et son ordre de grandeur ; et le rappel de l'hypothèse à chaque énoncé de la conclusion, y compris dans le sommaire et la lettre d'envoi. Une formulation du type « nous présumons que les travaux sont conformes » qui n'est suivie d'aucune de ces précisions n'est pas une hypothèse extraordinaire au sens de la norme : c'est une réserve déguisée, et elle n'exonère de rien, comme le rappelle la \cref{sec:10:responsabilite}. | |
| 217 | +\end{pratique} | |
| 218 | + | |
| 219 | +\subsection{Inspection et signature} | |
| 220 | + | |
| 221 | +Deux règles coercitives closent la norme 2. La règle 2.4 pose que « Tout immeuble évalué doit faire l'objet d'une inspection, à moins de circonstances exceptionnelles lesquelles seront expliquées au rapport » ; chaque signataire atteste avoir personnellement inspecté l'immeuble ou, à défaut, le mentionne explicitement ; l'inspection doit au minimum permettre de décrire l'immeuble, de fonder la conclusion sur l'usage le meilleur et le plus profitable et de soutenir les comparaisons \parencite[règle 2.4]{oeaqNormes}. Le \cref{ch:11} traite de l'inspection comme technique ; ici, il suffit de noter que la norme en fait une obligation dont l'omission doit être expliquée, et non une option tarifaire. Les évaluations « sur dossier » que certains prêteurs demandent pour des biens standard ne sont conformes que si le rapport dit clairement que l'immeuble n'a pas été inspecté, pourquoi, et quelles hypothèses en découlent. La règle 2.5 porte sur la signature : elle peut être manuscrite ou électronique selon un procédé reconnu par le secrétaire de l'Ordre, et le signataire « doit accepter l'entière responsabilité du contenu et des conclusions du rapport » \parencite[règle 2.5]{oeaqNormes}. Cette phrase a une portée considérable pour les cabinets où des stagiaires ou des techniciens préparent les rapports : la signature n'est pas une formalité de validation, elle transfère au signataire la responsabilité de chaque chiffre et de chaque phrase, ce que l'article 33 du Code de déontologie confirme en obligeant l'évaluateur à superviser et réviser le travail qu'il délègue \parencite[art.~33]{deontOEAQ}. | |
modified
manuscript/chapters/ch47.tex
+67 −56
@@ -1,8 +1,7 @@ | ||
| 1 | 1 | \chapter{Cas 3 : immeuble de 12 logements à Montréal}\label{ch:47} |
| 2 | 2 | % Renvois vers d'autres chapitres : \cref si le label est défini (compilation du livre complet), texte simple sinon (compilation isolée). |
| 3 | −\providecommand{\chref}[1]{\ifcsundef{r@ch:#1}{chapitre~#1}{\cref{ch:#1}}} | |
| 4 | 3 | |
| 5 | −Ce chapitre réunit, dans un seul dossier d'évaluation, l'ensemble des outils de la partie consacrée au revenu et au multilogement. L'immeuble étudié est un bâtiment de douze logements du quartier Rosemont--La Petite-Patrie, à Montréal, dont les loyers en place sont inférieurs au marché, dont la toiture arrive en fin de vie et dont le financement sera, comme c'est le cas pour la plupart des immeubles de six logements et plus en 2026, limité par la couverture de la dette plutôt que par le ratio prêt-valeur. Le lecteur suit l'évaluateur depuis la définition du mandat jusqu'à la rédaction de la conclusion : analyse de l'état locatif sous les contraintes du Code civil et du Tribunal administratif du logement, normalisation des dépenses, extraction du taux global sur quarante ventes, capitalisation directe, flux actualisés sur dix ans, comparaison par logement, financement, test de résistance par simulation de Monte-Carlo, puis réconciliation et rapport. Les chapitres de méthode (du \chref{26} au \chref{31}, puis le \chref{40}) ont présenté chacune de ces étapes isolément à partir des mêmes données ; le présent chapitre montre comment elles s'articulent et, surtout, comment les hypothèses de l'une contraignent les autres. Toutes les données de ce dossier sont synthétiques : l'immeuble n'existe pas, mais chaque ordre de grandeur est calibré sur des publications publiques citées au fil du texte. | |
| 4 | +Ce chapitre réunit, dans un seul dossier d'évaluation, l'ensemble des outils de la partie consacrée au revenu et au multilogement. L'immeuble étudié est un bâtiment de douze logements du quartier Rosemont--La Petite-Patrie, à Montréal, dont les loyers en place sont inférieurs au marché, dont la toiture arrive en fin de vie et dont le financement sera, comme c'est le cas pour la plupart des immeubles de six logements et plus en 2026, limité par la couverture de la dette plutôt que par le ratio prêt-valeur. Le lecteur suit l'évaluateur depuis la définition du mandat jusqu'à la rédaction de la conclusion : analyse de l'état locatif sous les contraintes du Code civil et du Tribunal administratif du logement, normalisation des dépenses, extraction du taux global sur quarante ventes, capitalisation directe, flux actualisés sur dix ans, comparaison par logement, financement, test de résistance par simulation de Monte-Carlo, puis réconciliation et rapport. Les chapitres de méthode (du \cref{ch:26} au \cref{ch:31}, puis le \cref{ch:40}) ont présenté chacune de ces étapes isolément à partir des mêmes données ; le présent chapitre montre comment elles s'articulent et, surtout, comment les hypothèses de l'une contraignent les autres. Toutes les données de ce dossier sont synthétiques : l'immeuble n'existe pas, mais chaque ordre de grandeur est calibré sur des publications publiques citées au fil du texte. | |
| 6 | 5 | |
| 7 | 6 | \begin{objectifs} |
| 8 | 7 | À la fin de ce chapitre, le lecteur sait : |
@@ -18,7 +17,7 @@ Ce chapitre réunit, dans un seul dossier d'évaluation, l'ensemble des outils d | ||
| 18 | 17 | \end{objectifs} |
| 19 | 18 | |
| 20 | 19 | \begin{synthetique}[Données synthétiques — immeuble fictif] |
| 21 | −Le sujet de ce chapitre et toutes les données qui l'entourent sont \emph{synthétiques}. Les fichiers \texttt{data/synthetic/cas3\_montreal\_sujet.json}, \texttt{cas3\_montreal\_etat\_locatif.csv} (douze baux), \texttt{cas3\_montreal\_depenses.csv} (trois exercices, 2023 à 2025), \texttt{cas3\_montreal\_comparables.csv} (quarante ventes d'immeubles de 6 à 30 logements, septembre 2024 à août 2026) et \texttt{cas3\_montreal\_hypotheses\_dcf.json} ont été générés par \texttt{analysis/python/traite/synth.py} avec la graine \texttt{20261031}. Les loyers de marché sont calibrés sur l'Enquête sur les logements locatifs de la SCHL d'octobre 2025 (zone Rosemont), les taux de capitalisation sur la fourchette CBRE du troisième trimestre 2026 pour le multirésidentiel de faible hauteur de catégorie B à Montréal, le taux hypothécaire sur les fonds avancés assurés déclarés à la Banque du Canada en août 2026, les taxes sur le taux général résidentiel 2026 de la Ville de Montréal et le taux de la taxe scolaire 2026-2027. Les lois de génération sont décrites dans \texttt{data/synthetic/README.md} ; les relations entre variables (décote d'ancienneté des baux, effet de l'état sur le taux) sont des hypothèses pédagogiques et non des estimations sur données réelles. Tous les chiffres du chapitre sont recalculés par \texttt{analysis/python/cases/ch47\_dossier.py}, qui s'appuie sur la bibliothèque partagée \texttt{traite/cas3.py} utilisée aussi du \chref{26} au \chref{31}. | |
| 20 | +Le sujet de ce chapitre et toutes les données qui l'entourent sont \emph{synthétiques}. Les fichiers \texttt{data/synthetic/cas3\_montreal\_sujet.json}, \texttt{cas3\_montreal\_etat\_locatif.csv} (douze baux), \texttt{cas3\_montreal\_depenses.csv} (trois exercices, 2023 à 2025), \texttt{cas3\_montreal\_comparables.csv} (quarante ventes d'immeubles de 6 à 30 logements, septembre 2024 à août 2026) et \texttt{cas3\_montreal\_hypotheses\_dcf.json} ont été générés par \texttt{analysis/python/traite/synth.py} avec la graine \texttt{20261031}. Les loyers de marché sont calibrés sur l'Enquête sur les logements locatifs de la SCHL d'octobre 2025 (zone Rosemont), les taux de capitalisation sur la fourchette CBRE du troisième trimestre 2026 pour le multirésidentiel de faible hauteur de catégorie B à Montréal, le taux hypothécaire sur les fonds avancés assurés déclarés à la Banque du Canada en août 2026, les taxes sur le taux général résidentiel 2026 de la Ville de Montréal et le taux de la taxe scolaire 2026-2027. Les lois de génération sont décrites dans \texttt{data/synthetic/README.md} ; les relations entre variables (décote d'ancienneté des baux, effet de l'état sur le taux) sont des hypothèses pédagogiques et non des estimations sur données réelles. Tous les chiffres du chapitre sont recalculés par \texttt{analysis/python/cases/ch47\_dossier.py}, qui s'appuie sur la bibliothèque partagée \texttt{traite/cas3.py} utilisée aussi du \cref{ch:26} au \cref{ch:31}. | |
| 22 | 21 | \end{synthetique} |
| 23 | 22 | |
| 24 | 23 | \section{Mandat et étendue de la mission}\label{sec:47:mandat} |
@@ -27,7 +26,7 @@ Le dossier s'ouvre, comme tout acte d'évaluation, par la définition de ce qui | ||
| 27 | 26 | |
| 28 | 27 | La base de valeur retenue est la valeur marchande telle que la définissent les Normes de pratique professionnelle de l'Ordre des évaluateurs agréés du Québec : « le prix sincère le plus probable, de la vente réelle ou présumée d'un immeuble, à une date donnée, sur un marché libre et ouvert à la concurrence » \parencite{oeaqNormes}, sous les quatre conditions que la norme énumère et que le rapport paraphrase : des parties bien informées de l'état de l'immeuble, des conditions du marché et de l'usage le plus probable ; une exposition suffisante sur le marché ; un paiement en argent comptant ou l'équivalent ; un prix épuré de tout financement avantageux ou de toute mesure incitative. Cette définition converge avec celle de la \emph{market value} des Normes internationales d'évaluation (IVS 102, annexe A10) \parencite{ivs2025} et avec celle des Normes uniformes de pratique professionnelle en matière d'évaluation au Canada \parencite{cuspap2026}. La distinction importe pour un prêteur : la valeur marchande n'est ni la valeur d'investissement propre à l'acheteur (qui dépend de sa fiscalité, de son financement et de ses projets de rattrapage des loyers), ni la valeur au rôle d'évaluation foncière, laquelle est établie à une autre date et, pour un immeuble locatif, sous l'hypothèse que « le vendeur est réputé détenir tous les droits du locataire » \parencite[art.~45.1]{lfm}, c'est-à-dire comme si l'immeuble était libre de baux. Nous reviendrons sur cet écart à la \cref{sec:47:reconciliation}. |
| 29 | 28 | |
| 30 | −Le droit évalué est la pleine propriété \parencite[art.~947]{ccq} grevée des douze baux résidentiels en vigueur. Au Québec, l'aliénation de l'immeuble « ne permet pas au nouveau locateur de résilier le bail » \parencite[art.~1937]{ccq} : l'acheteur acquiert donc le droit du locateur (\emph{leased fee}), c'est-à-dire le droit de percevoir les loyers contractuels, reconduits de plein droit et encadrés dans leur augmentation, et non le droit de relouer immédiatement au prix du marché. C'est ce droit, et aucun autre, que la valeur marchande doit refléter ; le \chref{2} a montré que l'écart entre la valeur du droit du locateur et la valeur libre de baux est la valeur actualisée du déficit de loyer, pondérée par la probabilité et le calendrier de sa résorption. | |
| 29 | +Le droit évalué est la pleine propriété \parencite[art.~947]{ccq} grevée des douze baux résidentiels en vigueur. Au Québec, l'aliénation de l'immeuble « ne permet pas au nouveau locateur de résilier le bail » \parencite[art.~1937]{ccq} : l'acheteur acquiert donc le droit du locateur (\emph{leased fee}), c'est-à-dire le droit de percevoir les loyers contractuels, reconduits de plein droit et encadrés dans leur augmentation, et non le droit de relouer immédiatement au prix du marché. C'est ce droit, et aucun autre, que la valeur marchande doit refléter ; le \cref{ch:02} a montré que l'écart entre la valeur du droit du locateur et la valeur libre de baux est la valeur actualisée du déficit de loyer, pondérée par la probabilité et le calendrier de sa résorption. | |
| 31 | 30 | |
| 32 | 31 | L'étendue du travail couvre l'inspection de l'immeuble (visite fictive du 25~août 2026, intérieur de neuf logements sur douze, aires communes, toiture, enveloppe, sous-sol technique), l'examen de l'état locatif et des baux fournis par le vendeur, l'examen des états de dépenses de 2023 à 2025, la consultation du rôle d'évaluation 2026-2028 et du compte de taxes 2026, la recherche de ventes d'immeubles comparables sur l'île de Montréal, la collecte des indicateurs de marché publiés par la SCHL, l'APCIQ, CBRE et la Banque du Canada, puis l'application de trois méthodes : la méthode du revenu par capitalisation directe et par actualisation des flux, et la méthode de comparaison par unité de revenu. La méthode du coût n'est pas appliquée : pour un immeuble locatif de 1962 dont le marché est actif et dont la valeur repose sur le revenu, la mesure de la dépréciation accumulée serait trop incertaine pour que l'indication ait un poids ; la Norme 2 exige que ce rejet soit justifié au rapport, ce qui est fait ici \parencite{oeaqNormes}. |
| 33 | 32 | |
@@ -63,7 +62,7 @@ Le terrain mesure \msq{740}. Le bâtiment, construit en 1962, compte trois étag | ||
| 63 | 62 | |
| 64 | 63 | \subsection{Zonage et droits acquis} |
| 65 | 64 | |
| 66 | −L'usage résidentiel multifamilial est autorisé dans la zone où se trouve l'immeuble ; la hauteur et le taux d'implantation du bâtiment sont conformes au règlement d'urbanisme de l'arrondissement en vigueur à la date d'évaluation. L'évaluateur n'a relevé aucune dérogation apparente ; si une composante (par exemple le nombre de cases de stationnement, inférieur au minimum exigé pour une construction neuve) s'avérait dérogatoire, elle serait protégée par les dispositions sur les droits acquis que la Loi sur l'aménagement et l'urbanisme autorise les municipalités à régir \parencite[art.~113, al.~2, par.~18\textsuperscript{o}]{lau}, sous réserve des règles de cessation en cas d'abandon ou d'interruption de l'usage. Nous ne développons pas davantage ce point, traité au \chref{8} : il suffit, pour le présent dossier, de constater que l'usage actuel est l'usage le meilleur et le plus profitable et que rien, dans l'état juridique de l'unité, ne restreint la continuation de cet usage. La conversion en copropriété divise est, en pratique, exclue : elle exige l'autorisation du Tribunal administratif du logement et est interdite dans l'agglomération de Montréal sauf dérogation municipale \parencite[art.~51 à 54.13]{loiTAL}. | |
| 65 | +L'usage résidentiel multifamilial est autorisé dans la zone où se trouve l'immeuble ; la hauteur et le taux d'implantation du bâtiment sont conformes au règlement d'urbanisme de l'arrondissement en vigueur à la date d'évaluation. L'évaluateur n'a relevé aucune dérogation apparente ; si une composante (par exemple le nombre de cases de stationnement, inférieur au minimum exigé pour une construction neuve) s'avérait dérogatoire, elle serait protégée par les dispositions sur les droits acquis que la Loi sur l'aménagement et l'urbanisme autorise les municipalités à régir \parencite[art.~113, al.~2, par.~18\textsuperscript{o}]{lau}, sous réserve des règles de cessation en cas d'abandon ou d'interruption de l'usage. Nous ne développons pas davantage ce point, traité au \cref{ch:08} : il suffit, pour le présent dossier, de constater que l'usage actuel est l'usage le meilleur et le plus profitable et que rien, dans l'état juridique de l'unité, ne restreint la continuation de cet usage. La conversion en copropriété divise est, en pratique, exclue : elle exige l'autorisation du Tribunal administratif du logement et est interdite dans l'agglomération de Montréal sauf dérogation municipale \parencite[art.~51 à 54.13]{loiTAL}. | |
| 67 | 66 | |
| 68 | 67 | \subsection{Rôle d'évaluation 2026-2028, taxes et droits de mutation} |
| 69 | 68 | |
@@ -77,7 +76,7 @@ L'inspection fictive du 25~août 2026 conduit à une cote d'état générale de | ||
| 77 | 76 | |
| 78 | 77 | \begin{table}[htbp]\centering |
| 79 | 78 | \caption{Composantes du bâtiment, état observé et dépenses en capital retenues (dossier synthétique).}\label{tab:47:inspection} |
| 80 | −\small\begin{tabular}{@{}L{3.6cm}L{1.6cm}L{4.4cm}S[table-format=6.0]c@{}} | |
| 79 | +\small\begin{tabular}{@{}L{3.5cm}L{1.5cm}L{4.3cm}S[table-format=6.0]c@{}} | |
| 81 | 80 | \toprule |
| 82 | 81 | Composante & {Année} & État observé & {CAPEX (\$)} & Exercice \\ |
| 83 | 82 | \midrule |
@@ -104,7 +103,7 @@ L'analyse de marché d'un immeuble locatif porte sur trois marchés distincts : | ||
| 104 | 103 | |
| 105 | 104 | \begin{table}[htbp]\centering |
| 106 | 105 | \caption{Indicateurs de marché retenus à la date d'évaluation (sources publiques, consultées le 31~octobre 2026).}\label{tab:47:marche} |
| 107 | −\footnotesize\begin{tabular}{@{}L{5.2cm}L{3.1cm}L{4.5cm}@{}} | |
| 106 | +\footnotesize\begin{tabular}{@{}L{5.1cm}L{3.0cm}L{4.5cm}@{}} | |
| 108 | 107 | \toprule |
| 109 | 108 | Indicateur & Valeur & Source et période \\ |
| 110 | 109 | \midrule |
@@ -132,7 +131,7 @@ Sur le marché des logements, le taux d'inoccupation de la région métropolitai | ||
| 132 | 131 | |
| 133 | 132 | Sur le marché des immeubles, les transactions de petits multilogements restent soutenues : le prix médian des plex de deux à cinq logements atteint \dollars{950000} dans le secteur Rosemont au troisième trimestre 2026 (+\pct{2} sur un an) \parencite{apciqbaromontreal2026}, indicateur de la vigueur de la demande des petits investisseurs et des acheteurs-occupants, même si les plex ne sont pas des comparables directs d'un douze logements. Pour les immeubles d'appartements, CBRE situe les taux de capitalisation du multirésidentiel de faible hauteur de catégorie B à Montréal entre \pct{4.25} et \pct{4.75} au troisième trimestre 2026, en légère hausse \parencite{cbrecaprates2026q3} ; l'écart avec l'obligation du Canada à dix ans (\pct{3.84}) n'est que d'une soixantaine de points de base pour le haut de la fourchette, ce qui illustre la compression persistante des taux résidentiels par rapport aux autres catégories d'actifs, phénomène documenté par \textcite{chervachidzeWheaton2013} et \textcite{ducaLing2020} dans le cycle précédent. |
| 134 | 133 | |
| 135 | −Sur le marché des capitaux, la Banque du Canada a maintenu son taux directeur à \pct{2.25} pendant toute la période précédant la date d'évaluation, puis l'a relevé à \pct{2.50} le 28~octobre 2026, première hausse depuis juillet 2023 \parencite{bdc20261028}. Le taux hypothécaire conventionnel à cinq ans affiché par les banques est de \pct{6.19}, mais les taux effectivement consentis sur les prêts assurés à taux fixe de cinq ans et plus s'établissaient à \pct{4.12} en août 2026 \parencite{bdctauxhypothecaires2026} ; c'est ce dernier taux, représentatif d'un prêt multilogement assuré par la SCHL, que le dossier retient pour l'analyse de financement. On note dès maintenant une tension centrale du dossier : un taux hypothécaire de \pct{4.12} amorti sur quarante ans correspond à une constante hypothécaire d'environ \pct{5.08}, supérieure au taux de capitalisation de \pct{4.42} que nous extrairons du marché ; le levier financier est donc négatif sur le revenu courant, ce que la \cref{sec:47:financement} mesure et dont le \chref{31} a exposé la théorie. | |
| 134 | +Sur le marché des capitaux, la Banque du Canada a maintenu son taux directeur à \pct{2.25} pendant toute la période précédant la date d'évaluation, puis l'a relevé à \pct{2.50} le 28~octobre 2026, première hausse depuis juillet 2023 \parencite{bdc20261028}. Le taux hypothécaire conventionnel à cinq ans affiché par les banques est de \pct{6.19}, mais les taux effectivement consentis sur les prêts assurés à taux fixe de cinq ans et plus s'établissaient à \pct{4.12} en août 2026 \parencite{bdctauxhypothecaires2026} ; c'est ce dernier taux, représentatif d'un prêt multilogement assuré par la SCHL, que le dossier retient pour l'analyse de financement. On note dès maintenant une tension centrale du dossier : un taux hypothécaire de \pct{4.12} amorti sur quarante ans correspond à une constante hypothécaire d'environ \pct{5.08}, supérieure au taux de capitalisation de \pct{4.42} que nous extrairons du marché ; le levier financier est donc négatif sur le revenu courant, ce que la \cref{sec:47:financement} mesure et dont le \cref{ch:31} a exposé la théorie. | |
| 136 | 135 | |
| 137 | 136 | \section{Analyse de l'état locatif}\label{sec:47:etat-locatif} |
| 138 | 137 | |
@@ -140,9 +139,9 @@ L'état locatif est la pièce maîtresse du dossier : il fixe le revenu que l'ac | ||
| 140 | 139 | |
| 141 | 140 | \begin{table}[htbp]\centering |
| 142 | 141 | \caption{État locatif au 1\textsuperscript{er} septembre 2026 : loyer contractuel, loyer de marché estimé et écart (données synthétiques).}\label{tab:47:etat-locatif} |
| 143 | −\small\begin{tabular}{@{}l c S[table-format=2.0] S[table-format=4.0] S[table-format=4.0] S[table-format=3.0] S[table-format=2.1] c S[table-format=2.0] c@{}} | |
| 142 | +\footnotesize\setlength{\tabcolsep}{3.5pt}\begin{tabular}{@{}l c S[table-format=3.0] S[table-format=5.0] S[table-format=5.0] S[table-format=4.0] S[table-format=2.1] c S[table-format=2.1] c@{}} | |
| 144 | 143 | \toprule |
| 145 | −Log. & Type & {m\textsuperscript{2}} & {Loyer contrat (\$/mois)} & {Loyer marché (\$/mois)} & {Écart (\$)} & {Écart (\%)} & Bail depuis & {Ancienneté (ans)} & Stat. \\ | |
| 144 | +Log. & Type & {m\textsuperscript{2}} & {Contrat (\$/mois)} & {Marché (\$/mois)} & {Écart (\$)} & {Écart (\%)} & Bail depuis & {Anc. (ans)} & Stat. \\ | |
| 146 | 145 | \midrule |
| 147 | 146 | 101 & 3½ & 55 & 917 & 1015 & 98 & 9.7 & 2021 & 5 & oui \\ |
| 148 | 147 | 102 & 3½ & 52 & 788 & 1004 & 216 & 21.5 & 2014 & 12 & oui \\ |
@@ -157,15 +156,16 @@ Log. & Type & {m\textsuperscript{2}} & {Loyer contrat (\$/mois)} & {Loyer march | ||
| 157 | 156 | 303 & 5½ & 96 & 1526 & 1622 & 96 & 5.9 & 2023 & 3 & oui \\ |
| 158 | 157 | 304 & 5½ & 93 & 1616 & 1683 & 67 & 4.0 & 2024 & 2 & non \\ |
| 159 | 158 | \midrule |
| 160 | −Total & & 825 & 13795 & 15503 & 1708 & 11.0 & & {6,3 (moy.)} & 6 \\ | |
| 159 | +Total & & 825 & 13795 & 15503 & 1708 & 11.0 & & 6.3 & 6 \\ | |
| 161 | 160 | \bottomrule |
| 162 | 161 | \end{tabular} |
| 163 | −\tabsource{\synth{\texttt{cas3\_montreal\_etat\_locatif.csv} ; échéance de tous les baux : 2027-06-30 ; chauffage non inclus ; script \texttt{ch47\_dossier.py}.}} | |
| 162 | +\tabsource{\synth{\texttt{cas3\_montreal\_etat\_locatif.csv} ; échéance de tous les baux : 2027-06-30 ; chauffage non inclus ; ligne « Total » : ancienneté moyenne 6,3 ans ; script \texttt{ch47\_dossier.py}.}} | |
| 164 | 163 | \end{table} |
| 165 | 164 | |
| 166 | 165 | \begin{figure}[htbp]\centering |
| 167 | −\includegraphics{ch47/ch47-fig-etat-locatif.pdf} | |
| 166 | +\includegraphics[width=\linewidth]{ch47/ch47-fig-etat-locatif.pdf} | |
| 168 | 167 | \caption{Loyer contractuel et loyer de marché estimé pour chacun des douze logements ; l'écart relatif, inscrit au-dessus de chaque paire, croît avec l'ancienneté du bail et atteint \pct{30.5} pour le logement 103 occupé depuis 2008.} |
| 168 | +\label{fig:47:etat-locatif} | |
| 169 | 169 | \figsource{\synth{script \texttt{analysis/python/figures/ch47\_dossier\_figures.py}, graine 20261031.}} |
| 170 | 170 | \end{figure} |
| 171 | 171 | |
@@ -185,7 +185,7 @@ Quatrièmement, la vente de l'immeuble ne modifie rien aux baux \parencite[art.~ | ||
| 185 | 185 | |
| 186 | 186 | \subsection{Vacance économique} |
| 187 | 187 | |
| 188 | −Le taux d'inoccupation de la SCHL mesure la part des logements vacants à une date donnée ; il ne mesure ni les pertes de loyer entre deux baux, ni les mauvaises créances, ni les concessions. L'évaluateur retient une provision combinée pour inoccupation et mauvaises créances de \pct{3.9} du revenu brut potentiel contractuel. Ce taux résulte du rapprochement de trois éléments : le taux d'inoccupation de \pct{2.9} de la région métropolitaine en octobre 2025 et de \pct{2.2} pour les immeubles de six à dix-neuf logements de l'île \parencite{schlLocatif2025,schlrmltables2025} ; la détente anticipée vers \pct{3.8} en 2026 et \pct{4.5} en 2027 \parencite{schlpmh2026ete} ; et une provision d'environ un point pour les mauvaises créances et les pertes à la rotation, qui, avec deux logements relocalisés par an et un mois perdu à chaque fois, représente à elle seule près de \pct{1.4} du revenu. Aucune statistique officielle ne mesure la vacance économique des petits immeubles québécois ; le \chref{27} a discuté les ordres de grandeur et leur incertitude, et le test de résistance fait varier ce taux de \pct{2.4} à \pct{6.3} au centile 5 et 95. | |
| 188 | +Le taux d'inoccupation de la SCHL mesure la part des logements vacants à une date donnée ; il ne mesure ni les pertes de loyer entre deux baux, ni les mauvaises créances, ni les concessions. L'évaluateur retient une provision combinée pour inoccupation et mauvaises créances de \pct{3.9} du revenu brut potentiel contractuel. Ce taux résulte du rapprochement de trois éléments : le taux d'inoccupation de \pct{2.9} de la région métropolitaine en octobre 2025 et de \pct{2.2} pour les immeubles de six à dix-neuf logements de l'île \parencite{schlLocatif2025,schlrmltables2025} ; la détente anticipée vers \pct{3.8} en 2026 et \pct{4.5} en 2027 \parencite{schlpmh2026ete} ; et une provision d'environ un point pour les mauvaises créances et les pertes à la rotation, qui, avec deux logements relocalisés par an et un mois perdu à chaque fois, représente à elle seule près de \pct{1.4} du revenu. Aucune statistique officielle ne mesure la vacance économique des petits immeubles québécois ; le \cref{ch:27} a discuté les ordres de grandeur et leur incertitude, et le test de résistance fait varier ce taux de \pct{2.4} à \pct{6.3} au centile 5 et 95. | |
| 189 | 189 | |
| 190 | 190 | \section{Revenus}\label{sec:47:revenus} |
| 191 | 191 | |
@@ -257,7 +257,7 @@ La gestion est imputée à \pct{4} du revenu brut effectif, soit \dollars{6737.7 | ||
| 257 | 257 | |
| 258 | 258 | \begin{table}[htbp]\centering |
| 259 | 259 | \caption{Normalisation des dépenses : base 2025, règle appliquée et montant de première année (données synthétiques).}\label{tab:47:depenses-normalisees} |
| 260 | −\small\begin{tabular}{@{}L{4.1cm}S[table-format=5.0]L{4.3cm}S[table-format=5.2]S[table-format=2.1]@{}} | |
| 260 | +\small\setlength{\tabcolsep}{4pt}\begin{tabular}{@{}L{4.0cm}S[table-format=5.0]L{4.1cm}S[table-format=5.2]S[table-format=2.1]@{}} | |
| 261 | 261 | \toprule |
| 262 | 262 | Poste & {2025 (\$)} & Règle de normalisation & {Normalisé (\$)} & {\% du RBE} \\ |
| 263 | 263 | \midrule |
@@ -284,8 +284,9 @@ Dépenses totales & 70693 & & 78488.19 & 46.6 \\ | ||
| 284 | 284 | \end{table} |
| 285 | 285 | |
| 286 | 286 | \begin{figure}[htbp]\centering |
| 287 | −\includegraphics{ch47/ch47-fig-depenses.pdf} | |
| 287 | +\includegraphics[width=\linewidth]{ch47/ch47-fig-depenses.pdf} | |
| 288 | 288 | \caption{Structure des dépenses normalisées de première année, par poste et par nature (fixes, variables et gestion imputée, réserve) ; le trait vertical indique le montant historique 2025 de chaque poste, ce qui fait ressortir la baisse des taxes recalculées au taux général et l'ajout de la gestion imputée.} |
| 289 | +\label{fig:47:depenses} | |
| 289 | 290 | \figsource{\synth{script \texttt{ch47\_dossier\_figures.py}, graine 20261031.}} |
| 290 | 291 | \end{figure} |
| 291 | 292 | |
@@ -311,13 +312,13 @@ La capitalisation directe convertit le revenu net de première année en valeur | ||
| 311 | 312 | \begin{equation}\label{eq:47:extraction} |
| 312 | 313 | R_j = \frac{\NOI_{1,j}}{P_j}, \qquad j = 1, \dots, n, |
| 313 | 314 | \end{equation} |
| 314 | −où $P_j$ est le prix de vente du comparable $j$ et $\NOI_{1,j}$ son revenu net de première année, normalisé par l'évaluateur selon les mêmes conventions que pour le sujet. Le fichier synthétique de comparables contient quarante ventes d'immeubles de 6 à 30 logements conclues entre septembre 2024 et août 2026 dans cinq quartiers de l'île (Rosemont, Villeray, Hochelaga, Plateau, Ahuntsic), avec pour chacune le revenu brut potentiel, le ratio de dépenses, le revenu net, le prix, le taux global, le prix par logement et le multiplicateur du revenu brut. La fenêtre de vingt-quatre mois et la tranche de 6 à 30 logements retenues par la fonction \texttt{taux\_extraits} conservent les quarante ventes. Le \cref{tab:47:comparables-taux} présente les dix ventes les plus proches du sujet, sélectionnées par la distance d'attributs du \chref{16} (nombre de logements, année de construction, ancienneté de la vente et état, avec un poids double pour la taille) ; le \cref{tab:47:stats-taux} donne les statistiques de l'ensemble et la \cref{fig:47:taux-extraits} la dispersion complète. | |
| 315 | +où $P_j$ est le prix de vente du comparable $j$ et $\NOI_{1,j}$ son revenu net de première année, normalisé par l'évaluateur selon les mêmes conventions que pour le sujet. Le fichier synthétique de comparables contient quarante ventes d'immeubles de 6 à 30 logements conclues entre septembre 2024 et août 2026 dans cinq quartiers de l'île (Rosemont, Villeray, Hochelaga, Plateau, Ahuntsic), avec pour chacune le revenu brut potentiel, le ratio de dépenses, le revenu net, le prix, le taux global, le prix par logement et le multiplicateur du revenu brut. La fenêtre de vingt-quatre mois et la tranche de 6 à 30 logements retenues par la fonction \texttt{taux\_extraits} conservent les quarante ventes. Le \cref{tab:47:comparables-taux} présente les dix ventes les plus proches du sujet, sélectionnées par la distance d'attributs du \cref{ch:16} (nombre de logements, année de construction, ancienneté de la vente et état, avec un poids double pour la taille) ; le \cref{tab:47:stats-taux} donne les statistiques de l'ensemble et la \cref{fig:47:taux-extraits} la dispersion complète. | |
| 315 | 316 | |
| 316 | 317 | \begin{table}[htbp]\centering |
| 317 | 318 | \caption{Les dix ventes comparables les plus proches du sujet (sur quarante), classées par distance d'attributs (données synthétiques).}\label{tab:47:comparables-taux} |
| 318 | −\footnotesize\begin{tabular}{@{}l l l S[table-format=2.0] S[table-format=4.0] c S[table-format=6.0] S[table-format=7.0] S[table-format=1.2] S[table-format=6.0] S[table-format=2.2]@{}} | |
| 319 | +\footnotesize\setlength{\tabcolsep}{3pt}\begin{tabular}{@{}l l l S[table-format=2.0] S[table-format=4.0] c S[table-format=6.0] S[table-format=7.0] S[table-format=1.2] S[table-format=6.0] S[table-format=2.2]@{}} | |
| 319 | 320 | \toprule |
| 320 | −Vente & Date & Quartier & {Log.} & {Constr.} & État & {RNE (\$)} & {Prix (\$)} & {Taux (\%)} & {Prix/log. (\$)} & {MRB} \\ | |
| 321 | +Vente & Date & Quartier & {Log.} & {Constr.} & État & {RNE (\$)} & {Prix (\$)} & {Taux (\%)} & {\$/log.} & {MRB} \\ | |
| 321 | 322 | \midrule |
| 322 | 323 | M040 & 2026-08-09 & Villeray & 12 & 1954 & 3 & 111423 & 2518000 & 4.43 & 209833 & 11.87 \\ |
| 323 | 324 | M035 & 2026-04-30 & Plateau & 13 & 1947 & 3 & 98988 & 2354000 & 4.21 & 181077 & 13.43 \\ |
@@ -336,7 +337,7 @@ M037 & 2026-07-28 & Hochelaga & 16 & 1942 & 3 & 157028 & 3923000 & 4.00 & 245188 | ||
| 336 | 337 | |
| 337 | 338 | \begin{table}[htbp]\centering |
| 338 | 339 | \caption{Statistiques des taux globaux extraits des quarante ventes et indications de valeur correspondantes pour le sujet (données synthétiques).}\label{tab:47:stats-taux} |
| 339 | −\small\begin{tabular}{@{}L{6.4cm}S[table-format=1.3]S[table-format=7.0]@{}} | |
| 340 | +\small\begin{tabular}{@{}L{6.4cm}S[table-format=-1.3]S[table-format=7.0]@{}} | |
| 340 | 341 | \toprule |
| 341 | 342 | Statistique (n = 40) & {Taux (\%)} & {$\NOI_1 / R$ (\$)} \\ |
| 342 | 343 | \midrule |
@@ -361,8 +362,9 @@ Régression : constante (état 3, 12 logements) & 4.567 & 1969694 \\ | ||
| 361 | 362 | \end{table} |
| 362 | 363 | |
| 363 | 364 | \begin{figure}[htbp]\centering |
| 364 | −\includegraphics{ch47/ch47-fig-taux-extraits.pdf} | |
| 365 | +\includegraphics[width=\linewidth]{ch47/ch47-fig-taux-extraits.pdf} | |
| 365 | 366 | \caption{Taux globaux extraits des quarante ventes selon le nombre de logements et l'état ; la bande grise est l'intervalle interquartile, le trait pointillé la médiane retenue pour le sujet, repéré par le losange à douze logements. Les grands immeubles du Plateau et d'Ahuntsic tirent le bas de la distribution.} |
| 367 | +\label{fig:47:taux-extraits} | |
| 366 | 368 | \figsource{\synth{script \texttt{ch47\_dossier\_figures.py}, graine 20261031.}} |
| 367 | 369 | \end{figure} |
| 368 | 370 | |
@@ -370,13 +372,13 @@ Régression : constante (état 3, 12 logements) & 4.567 & 1969694 \\ | ||
| 370 | 372 | |
| 371 | 373 | La distribution des taux est resserrée : la moitié centrale des ventes se situe entre \pct{4.152} et \pct{4.638}, l'écart-type est de 41~points de base et la fourchette entière, de \pct{3.72} à \pct{5.47}, s'inscrit presque exactement dans l'intervalle publié par CBRE pour le multirésidentiel de faible hauteur à Montréal, \pct{4.25} à \pct{4.75} pour la catégorie B, les quelques ventes sous \pct{4} correspondant à de grands immeubles du Plateau qui relèvent plutôt de la catégorie A \parencite{cbrecaprates2026q3}. Trois sous-échantillons éclairent la position du sujet. Les immeubles de 6 à 14 logements se vendent à une médiane de \pct{4.555}, soit 14~points de base au-dessus de l'ensemble, ce que la pente de la régression confirme (environ 2~points de base de taux par logement supplémentaire) : les petits immeubles sont moins liquides et moins recherchés des investisseurs institutionnels. Les ventes des douze derniers mois donnent une médiane de \pct{4.43}, pratiquement égale à celle de l'ensemble : aucune dérive temporelle n'est perceptible, en accord avec la lecture « stable à légèrement en hausse » du marché. Le quartier Rosemont affiche une médiane de \pct{4.59}, mais sur onze ventes dont plusieurs grands immeubles anciens ; l'effet de quartier est confondu avec l'effet de taille et l'évaluateur ne lui accorde pas de poids distinct. Quant à l'état, la pente estimée est faible et de signe négatif (un meilleur état abaisse le taux), mais elle n'est pas statistiquement distinguable de zéro sur quarante observations. |
| 372 | 374 | |
| 373 | −L'évaluateur retient la médiane de l'ensemble, \pct{4.415}, arrondie à \pct{4.42} dans la conclusion. Ce choix se justifie par la robustesse de la médiane aux valeurs extrêmes, par la cohérence avec la fourchette CBRE et avec la médiane des ventes récentes, et par la position du sujet : un immeuble de douze logements, d'état moyen, à Rosemont, dont les loyers sont sous le marché. Ce dernier trait mérite réflexion. Un acheteur qui anticipe un rattrapage des loyers accepte un taux de première année plus bas qu'il ne le ferait pour un immeuble déjà au marché, puisque la croissance attendue du revenu est plus forte ; la relation $R \approx r - g$ du modèle de \textcite{gordonShapiro1956}, dérivée au \chref{29}, l'exprime directement. Les comparables synthétiques ont été générés avec des loyers en place eux aussi décotés selon l'ancienneté, de sorte que leur taux global intègre déjà, en moyenne, une attente de rattrapage comparable ; appliquer la médiane au sujet revient à lui prêter un potentiel de croissance moyen, ce qui est prudent compte tenu de l'écart de \pct{11} au marché. L'évaluateur aurait pu, à l'inverse, retenir un taux légèrement supérieur pour la petite taille (vers \pct{4.55}) ; il s'en abstient parce que la méthode des flux actualisés, à la section suivante, rend explicite la trajectoire des loyers et offre un contrepoids à ce jugement. | |
| 375 | +L'évaluateur retient la médiane de l'ensemble, \pct{4.415}, arrondie à \pct{4.42} dans la conclusion. Ce choix se justifie par la robustesse de la médiane aux valeurs extrêmes, par la cohérence avec la fourchette CBRE et avec la médiane des ventes récentes, et par la position du sujet : un immeuble de douze logements, d'état moyen, à Rosemont, dont les loyers sont sous le marché. Ce dernier trait mérite réflexion. Un acheteur qui anticipe un rattrapage des loyers accepte un taux de première année plus bas qu'il ne le ferait pour un immeuble déjà au marché, puisque la croissance attendue du revenu est plus forte ; la relation $R \approx r - g$ du modèle de \textcite{gordonShapiro1956}, dérivée au \cref{ch:29}, l'exprime directement. Les comparables synthétiques ont été générés avec des loyers en place eux aussi décotés selon l'ancienneté, de sorte que leur taux global intègre déjà, en moyenne, une attente de rattrapage comparable ; appliquer la médiane au sujet revient à lui prêter un potentiel de croissance moyen, ce qui est prudent compte tenu de l'écart de \pct{11} au marché. L'évaluateur aurait pu, à l'inverse, retenir un taux légèrement supérieur pour la petite taille (vers \pct{4.55}) ; il s'en abstient parce que la méthode des flux actualisés, à la section suivante, rend explicite la trajectoire des loyers et offre un contrepoids à ce jugement. | |
| 374 | 376 | |
| 375 | 377 | L'indication par capitalisation directe est |
| 376 | 378 | \begin{equation}\label{eq:47:vcap} |
| 377 | 379 | \hat V_{\text{cap}} = \frac{\NOI_1}{R} = \frac{\dollars{89955.75}}{\num{0.04415}} = \dollars{2037502.89}, |
| 378 | 380 | \end{equation} |
| 379 | −soit \dollars{169792} par logement et un multiplicateur de \num{12.31} fois le revenu brut potentiel contractuel. La sensibilité au taux est immédiate : à \pct{4.165} l'indication passe à \dollars{2159802}, à \pct{4.665} à \dollars{1928312}, soit une variation d'environ \pct{5.7} pour 25~points de base ; les bornes interquartiles donnent \dollars{2166303} et \dollars{1939747}. Cette plage de plus ou moins \pct{6} autour de la médiane constitue l'incertitude propre à la méthode, indépendamment de celle du revenu net ; le \chref{20} a montré comment combiner les deux. | |
| 381 | +soit \dollars{169792} par logement et un multiplicateur de \num{12.31} fois le revenu brut potentiel contractuel. La sensibilité au taux est immédiate : à \pct{4.165} l'indication passe à \dollars{2159802}, à \pct{4.665} à \dollars{1928312}, soit une variation d'environ \pct{5.7} pour 25~points de base ; les bornes interquartiles donnent \dollars{2166303} et \dollars{1939747}. Cette plage de plus ou moins \pct{6} autour de la médiane constitue l'incertitude propre à la méthode, indépendamment de celle du revenu net ; le \cref{ch:20} a montré comment combiner les deux. | |
| 380 | 382 | |
| 381 | 383 | \section{Méthode du revenu : flux actualisés sur dix ans}\label{sec:47:dcf} |
| 382 | 384 | |
@@ -418,9 +420,9 @@ Le \cref{tab:47:projection} présente la projection complète et la \cref{fig:47 | ||
| 418 | 420 | |
| 419 | 421 | \begin{table}[htbp]\centering |
| 420 | 422 | \caption{Projection des flux sur dix ans et revenu de l'année 11 servant à la valeur terminale (données synthétiques, \$ courants).}\label{tab:47:projection} |
| 421 | −\footnotesize\begin{tabular}{@{}c c S[table-format=6.0] S[table-format=6.0] S[table-format=6.0] S[table-format=5.0] S[table-format=4.0] S[table-format=6.0] S[table-format=6.0] S[table-format=6.0]@{}} | |
| 423 | +\footnotesize\setlength{\tabcolsep}{3pt}\begin{tabular}{@{}c c S[table-format=6.0] S[table-format=6.0] S[table-format=6.0] S[table-format=5.0] S[table-format=4.0] S[table-format=6.0] S[table-format=6.0] S[table-format=6.0]@{}} | |
| 422 | 424 | \toprule |
| 423 | −$t$ & Exercice & {RBP} & {RBP marché} & {RBE} & {Dép. hors rés.} & {Réserve} & {RNE} & {CAPEX} & {Flux avant dette} \\ | |
| 425 | +$t$ & Exerc. & {RBP} & {RBP marché} & {RBE} & {Dép.} & {Rés.} & {RNE} & {CAPEX} & {Flux av. dette} \\ | |
| 424 | 426 | \midrule |
| 425 | 427 | 1 & 2026-27 & 165540 & 186036 & 168444 & 70385 & 8103 & 89956 & 0 & 89956 \\ |
| 426 | 428 | 2 & 2027-28 & 177389 & 192547 & 180112 & 72497 & 8103 & 99512 & 85000 & 14512 \\ |
@@ -433,15 +435,16 @@ $t$ & Exercice & {RBP} & {RBP marché} & {RBE} & {Dép. hors rés.} & {Réserve} | ||
| 433 | 435 | 9 & 2034-35 & 242513 & 244974 & 244912 & 89162 & 8103 & 147647 & 0 & 147647 \\ |
| 434 | 436 | 10 & 2035-36 & 251101 & 253548 & 253521 & 91837 & 8103 & 153581 & 0 & 153581 \\ |
| 435 | 437 | \midrule |
| 436 | −11 & 2036-37 & 259911 & 262422 & 262353 & 94592 & 8103 & 159658 & {—} & {(valeur terminale)} \\ | |
| 438 | +11 & 2036-37 & 259911 & 262422 & 262353 & 94592 & 8103 & 159658 & {—} & {(val. term.)} \\ | |
| 437 | 439 | \bottomrule |
| 438 | 440 | \end{tabular} |
| 439 | 441 | \tabsource{\synth{\texttt{traite/cas3.py}, fonction \texttt{projection\_dcf} ; montants arrondis au dollar, calculs non arrondis.}} |
| 440 | 442 | \end{table} |
| 441 | 443 | |
| 442 | 444 | \begin{figure}[htbp]\centering |
| 443 | −\includegraphics{ch47/ch47-fig-flux-dcf.pdf} | |
| 445 | +\includegraphics[width=\linewidth]{ch47/ch47-fig-flux-dcf.pdf} | |
| 444 | 446 | \caption{Revenu net, dépenses en capital et flux avant dette de la projection sur dix ans ; la ligne tiretée est le service annuel de la dette du prêt dimensionné au ratio de couverture de 1,20. Les années de travaux (toiture en 2027, balcons en 2029, cuisines en 2032) font passer le flux avant dette sous le service de la dette.} |
| 447 | +\label{fig:47:flux-dcf} | |
| 445 | 448 | \figsource{\synth{script \texttt{ch47\_dossier\_figures.py}, graine 20261031.}} |
| 446 | 449 | \end{figure} |
| 447 | 450 | |
@@ -463,7 +466,7 @@ La méthode de comparaison appliquée à un immeuble de douze logements ne compa | ||
| 463 | 466 | |
| 464 | 467 | \subsection{Extraction des ajustements} |
| 465 | 468 | |
| 466 | −Faute de paires de ventes appariées, les ajustements sont extraits par une régression auxiliaire du logarithme du prix par logement sur l'état, la taille, le niveau de loyer, l'ancienneté de la vente et le quartier, estimée sur les quarante ventes ($R^2 = \num{0.635}$, écart-type résiduel \pct{10.9}). Trois coefficients sont exploitables : le niveau de loyer moyen, avec une élasticité de \num{1.17} (erreur-type \num{0.25}), ce qui signifie qu'un immeuble dont les loyers sont \pct{10} plus élevés se vend environ \pct{12} plus cher par logement ; la taille, avec un effet de \pct{0.79} par logement supplémentaire (erreur-type \pct{0.26}), qui traduit la prime des grands immeubles ; et le quartier, le Plateau (+\pct{17.5}) et Ahuntsic (+\pct{10.4}) se vendant plus cher que Rosemont, Hochelaga (+\pct{3.5}) et Villeray (+\pct{0.7}) étant statistiquement indiscernables de Rosemont. L'effet temporel estimé est négatif et non significatif ($-\pct{2.8}$ par an, erreur-type \pct{3.1}) : conformément à la lecture d'un marché stable (indice Teranet--Banque Nationale de Montréal à +\pct{1.4} sur un an en septembre 2026 \parencite{teranet202609}), aucun ajustement de conditions de marché n'est appliqué. L'effet de l'état n'est pas identifiable sur l'échantillon ; l'évaluateur retient un ajustement déclaré de \pct{3} du prix par point d'état, hypothèse pédagogique cohérente avec la loi de génération du jeu synthétique et avec les ordres de grandeur du \chref{17}. | |
| 469 | +Faute de paires de ventes appariées, les ajustements sont extraits par une régression auxiliaire du logarithme du prix par logement sur l'état, la taille, le niveau de loyer, l'ancienneté de la vente et le quartier, estimée sur les quarante ventes ($R^2 = \num{0.635}$, écart-type résiduel \pct{10.9}). Trois coefficients sont exploitables : le niveau de loyer moyen, avec une élasticité de \num{1.17} (erreur-type \num{0.25}), ce qui signifie qu'un immeuble dont les loyers sont \pct{10} plus élevés se vend environ \pct{12} plus cher par logement ; la taille, avec un effet de \pct{0.79} par logement supplémentaire (erreur-type \pct{0.26}), qui traduit la prime des grands immeubles ; et le quartier, le Plateau (+\pct{17.5}) et Ahuntsic (+\pct{10.4}) se vendant plus cher que Rosemont, Hochelaga (+\pct{3.5}) et Villeray (+\pct{0.7}) étant statistiquement indiscernables de Rosemont. L'effet temporel estimé est négatif et non significatif ($-\pct{2.8}$ par an, erreur-type \pct{3.1}) : conformément à la lecture d'un marché stable (indice Teranet--Banque Nationale de Montréal à +\pct{1.4} sur un an en septembre 2026 \parencite{teranet202609}), aucun ajustement de conditions de marché n'est appliqué. L'effet de l'état n'est pas identifiable sur l'échantillon ; l'évaluateur retient un ajustement déclaré de \pct{3} du prix par point d'état, hypothèse pédagogique cohérente avec la loi de génération du jeu synthétique et avec les ordres de grandeur du \cref{ch:17}. | |
| 467 | 470 | |
| 468 | 471 | Les ajustements sont appliqués dans l'ordre conventionnel de l'ouvrage : d'abord les ajustements de transaction en pourcentage (ici nuls), puis les ajustements de propriété en dollars par logement, additifs, le signe étant celui du point de vue du sujet : |
| 469 | 472 | \begin{equation}\label{eq:47:ajuste} |
@@ -473,9 +476,9 @@ avec $A_{j,\text{loc}} = P_j\,[e^{-\hat\mu_{q(j)}} - 1]$ pour ramener le compara | ||
| 473 | 476 | |
| 474 | 477 | \begin{table}[htbp]\centering |
| 475 | 478 | \caption{Grille d'ajustement des prix par logement (données synthétiques, \$ par logement).}\label{tab:47:grille} |
| 476 | −\footnotesize\begin{tabular}{@{}l S[table-format=6.0] S[table-format=-5.0] S[table-format=-4.0] S[table-format=-4.0] S[table-format=-5.0] S[table-format=6.0] S[table-format=2.1] S[table-format=1.3]@{}} | |
| 479 | +\footnotesize\setlength{\tabcolsep}{3pt}\begin{tabular}{@{}l S[table-format=6.0] S[table-format=-5.0] S[table-format=-4.0] S[table-format=-4.0] S[table-format=-5.0] S[table-format=6.0] S[table-format=2.1] S[table-format=1.3]@{}} | |
| 477 | 480 | \toprule |
| 478 | −Vente & {Prix/log.} & {Localisation} & {Taille} & {État} & {Loyer} & {Prix ajusté} & {Ajust. brut (\%)} & {Poids} \\ | |
| 481 | +Vente & {Prix/log.} & {Local.} & {Taille} & {État} & {Loyer} & {Prix aj.} & {Brut (\%)} & {Poids} \\ | |
| 479 | 482 | \midrule |
| 480 | 483 | M040 & 209833 & -1528 & 0 & 0 & -52858 & 155448 & 25.9 & 0.072 \\ |
| 481 | 484 | M035 & 181077 & -29127 & -1429 & 0 & 4834 & 155355 & 19.5 & 0.095 \\ |
@@ -488,8 +491,8 @@ M023 & 292583 & -28833 & 0 & -8777 & -81442 & 173530 & 40.7 & 0.046 \\ | ||
| 488 | 491 | M031 & 139333 & -4751 & 3352 & -4180 & -6699 & 127056 & 13.6 & 0.137 \\ |
| 489 | 492 | M037 & 245188 & -8360 & -7650 & 0 & -33636 & 195542 & 20.3 & 0.092 \\ |
| 490 | 493 | \midrule |
| 491 | −\multicolumn{6}{@{}l}{Prix ajusté pondéré (poids inversement proportionnels à l'ajustement brut)} & 155909 & & 1.000 \\ | |
| 492 | −\multicolumn{6}{@{}l}{Moyenne simple / médiane / minimum / maximum} & {159083 / 155401 / 127056 / 195542} & & \\ | |
| 494 | +\multicolumn{6}{@{}l}{Prix ajusté pondéré (poids $\propto 1/$ajustement brut)} & 155909 & & 1.000 \\ | |
| 495 | +\multicolumn{6}{@{}l}{Moyenne simple 159~083 ; médiane 155~401 ; min. 127~056 ; max. 195~542} & & & \\ | |
| 493 | 496 | \bottomrule |
| 494 | 497 | \end{tabular} |
| 495 | 498 | \tabsource{\synth{\texttt{traite.comparables} (\texttt{ComparableAjuste}, \texttt{poids\_inverse\_brut}, \texttt{reconciliation}) ; script \texttt{ch47\_dossier.py}.}} |
@@ -497,7 +500,7 @@ M037 & 245188 & -8360 & -7650 & 0 & -33636 & 195542 & 20.3 & 0.092 \\ | ||
| 497 | 500 | |
| 498 | 501 | \subsection{Indication par le prix par logement et par le multiplicateur} |
| 499 | 502 | |
| 500 | −Le \cref{tab:47:grille} montre que les prix ajustés se resserrent entre \dollars{150455} et \dollars{173530} pour huit des dix comparables, l'immeuble de neuf logements d'Hochelaga (M031) tirant vers le bas et le seize logements d'Hochelaga (M037) vers le haut. La pondération inversement proportionnelle à l'ajustement brut, règle usuelle du \chref{20}, donne un prix par logement de \dollars{155909}, l'écart-type pondéré étant de \dollars{17152} (\pct{11}). L'indication est | |
| 503 | +Le \cref{tab:47:grille} montre que les prix ajustés se resserrent entre \dollars{150455} et \dollars{173530} pour huit des dix comparables, l'immeuble de neuf logements d'Hochelaga (M031) tirant vers le bas et le seize logements d'Hochelaga (M037) vers le haut. La pondération inversement proportionnelle à l'ajustement brut, règle usuelle du \cref{ch:20}, donne un prix par logement de \dollars{155909}, l'écart-type pondéré étant de \dollars{17152} (\pct{11}). L'indication est | |
| 501 | 504 | \begin{equation}\label{eq:47:vunite} |
| 502 | 505 | \hat V_{\text{unité}} = 12 \times \dollars{155909} = \dollars{1870903}. |
| 503 | 506 | \end{equation} |
@@ -511,15 +514,20 @@ L'indication par unité est inférieure de \pct{8.2} à la capitalisation direct | ||
| 511 | 514 | |
| 512 | 515 | Le prêteur fictif offre un prêt de premier rang assuré, au taux de \pct{4.12} fixe pour un terme de cinq ans, amorti sur quarante ans, sous deux contraintes : un ratio prêt-valeur (RPV, \emph{loan-to-value}) maximal de \pct{75} et un ratio de couverture de la dette (RCD, \emph{debt service coverage ratio}) minimal de \num{1.20}. Les hypothèques canadiennes à taux fixe portent intérêt composé semestriellement\footnote{En vertu de la Loi sur l'intérêt (L.R.C. 1985, c.~I-15, art.~6), qui impose d'exprimer le taux en taux annuel calculé semestriellement ou annuellement ; voir l'annexe des conventions de calcul.}, de sorte que le taux mensuel équivalent et le paiement mensuel constant sur $n = 480$ mois sont |
| 513 | 516 | \begin{equation}\label{eq:47:paiement} |
| 514 | −i_m = \left(1 + \frac{i}{2}\right)^{1/6} - 1 = \num{0.0034042}, \qquad PMT = M_0\,\frac{i_m}{1 - (1+i_m)^{-n}}, \qquad f = \frac{12\,PMT}{M_0} = \pct{5.0789}, | |
| 517 | +\begin{split} | |
| 518 | +i_m &= \left(1 + \frac{i}{2}\right)^{1/6} - 1 = \num{0.0034042}, \\ | |
| 519 | +PMT &= M_0\,\frac{i_m}{1 - (1+i_m)^{-n}}, \qquad f = \frac{12\,PMT}{M_0} = \pct{5.0789}, | |
| 520 | +\end{split} | |
| 515 | 521 | \end{equation} |
| 516 | 522 | où $f$ est la constante hypothécaire, indépendante du montant emprunté. Le prêt maximal est le plus petit des deux montants autorisés : |
| 517 | 523 | \begin{equation}\label{eq:47:pretmax} |
| 518 | −M_{\max} = \min\!\left( \LTV_{\max}\, \hat V,\ \frac{\NOI_1}{\DSCR_{\min}\, f} \right) | |
| 519 | −= \min\!\left( \num{0.75} \times \dollars{2208566},\ \frac{\dollars{89955.75}}{\num{1.20} \times \num{0.050789}} \right) | |
| 520 | −= \min(\dollars{1656424},\ \dollars{1475959}). | |
| 524 | +\begin{split} | |
| 525 | +M_{\max} &= \min\!\left( \LTV_{\max}\, \hat V,\ \frac{\NOI_1}{\DSCR_{\min}\, f} \right) | |
| 526 | += \min\!\left( \num{0.75} \times \dollars{2208566},\ \frac{\dollars{89955.75}}{\num{1.20} \times \num{0.050789}} \right) \\ | |
| 527 | +&= \min(\dollars{1656424},\ \dollars{1475959}) = \dollars{1475959}. | |
| 528 | +\end{split} | |
| 521 | 529 | \end{equation} |
| 522 | −La contrainte liante est la couverture de la dette : le prêt est plafonné à \dollars{1475959}, soit un ratio prêt-valeur effectif de \pct{66.8} sur l'indication DCF, bien en deçà des \pct{75} offerts. Ce résultat est général en 2026 pour les immeubles dont le taux de capitalisation est inférieur à la constante hypothécaire : la condition $\DSCR \geq 1{,}20$ s'écrit $M_0 / V \leq R / (1{,}20\, f)$, soit ici $\num{0.04415} / (1{,}20 \times \num{0.050789}) = \pct{72.4}$ sur la valeur par capitalisation, et moins encore lorsque la valeur retenue excède la capitalisation du revenu courant. Dans la suite de cette section, le financement est présenté, comme au \chref{31}, sur la base de l'indication DCF afin de conserver des chiffres identiques d'un chapitre à l'autre ; la \cref{sec:47:reconciliation} le recalcule à la valeur finale. Le \cref{tab:47:financement} résume les paramètres. | |
| 530 | +La contrainte liante est la couverture de la dette : le prêt est plafonné à \dollars{1475959}, soit un ratio prêt-valeur effectif de \pct{66.8} sur l'indication DCF, bien en deçà des \pct{75} offerts. Ce résultat est général en 2026 pour les immeubles dont le taux de capitalisation est inférieur à la constante hypothécaire : la condition $\DSCR \geq 1{,}20$ s'écrit $M_0 / V \leq R / (1{,}20\, f)$, soit ici $\num{0.04415} / (1{,}20 \times \num{0.050789}) = \pct{72.4}$ sur la valeur par capitalisation, et moins encore lorsque la valeur retenue excède la capitalisation du revenu courant. Dans la suite de cette section, le financement est présenté, comme au \cref{ch:31}, sur la base de l'indication DCF afin de conserver des chiffres identiques d'un chapitre à l'autre ; la \cref{sec:47:reconciliation} le recalcule à la valeur finale. Le \cref{tab:47:financement} résume les paramètres. | |
| 523 | 531 | |
| 524 | 532 | \begin{table}[htbp]\centering |
| 525 | 533 | \caption{Dimensionnement du prêt et indicateurs de levier, sur la base de l'indication DCF (données synthétiques).}\label{tab:47:financement} |
@@ -556,7 +564,7 @@ TRI sans levier (achat à la valeur DCF) & {\pct{7.25}} \\ | ||
| 556 | 564 | |
| 557 | 565 | \subsection{Levier, flux après dette et point mort} |
| 558 | 566 | |
| 559 | −Le service annuel de la dette est de \dollars{74963.13}, dont \dollars{60016} d'intérêts et \dollars{14947} de capital la première année. Le flux après dette de la première année n'est que de \dollars{14992.63}, soit un rendement courant des fonds propres de \pct{2.05} sur une mise de fonds de \dollars{732606} : à ce prix, l'acheteur accepte un rendement courant inférieur à celui d'une obligation d'État et mise entièrement sur la croissance du revenu et sur la plus-value. La raison est le levier négatif sur le revenu courant : la constante hypothécaire de \pct{5.08} dépasse le taux de capitalisation de \pct{4.42}, de sorte que chaque dollar emprunté coûte, en service de la dette, plus qu'il ne rapporte en revenu net. Le \chref{31} a démontré que le rendement courant des fonds propres s'écrit | |
| 567 | +Le service annuel de la dette est de \dollars{74963.13}, dont \dollars{60016} d'intérêts et \dollars{14947} de capital la première année. Le flux après dette de la première année n'est que de \dollars{14992.63}, soit un rendement courant des fonds propres de \pct{2.05} sur une mise de fonds de \dollars{732606} : à ce prix, l'acheteur accepte un rendement courant inférieur à celui d'une obligation d'État et mise entièrement sur la croissance du revenu et sur la plus-value. La raison est le levier négatif sur le revenu courant : la constante hypothécaire de \pct{5.08} dépasse le taux de capitalisation de \pct{4.42}, de sorte que chaque dollar emprunté coûte, en service de la dette, plus qu'il ne rapporte en revenu net. Le \cref{ch:31} a démontré que le rendement courant des fonds propres s'écrit | |
| 560 | 568 | \begin{equation}\label{eq:47:levier} |
| 561 | 569 | R_E = \frac{\NOI_1 - f M_0}{V - M_0} = R + (R - f)\frac{M_0}{V - M_0}, |
| 562 | 570 | \end{equation} |
@@ -580,9 +588,9 @@ Le terme de cinq ans expose l'emprunteur à un renouvellement en 2031 à un taux | ||
| 580 | 588 | |
| 581 | 589 | \begin{table}[htbp]\centering |
| 582 | 590 | \caption{Renouvellement du prêt à l'échéance du terme de cinq ans : service de la dette et ratios de couverture selon le taux renouvelé (données synthétiques).}\label{tab:47:scenarios} |
| 583 | −\small\begin{tabular}{@{}l S[table-format=1.2] S[table-format=6.0] S[table-format=5.0] S[table-format=1.3] S[table-format=1.3] S[table-format=1.3]@{}} | |
| 591 | +\footnotesize\setlength{\tabcolsep}{3pt}\begin{tabular}{@{}l S[table-format=1.2] S[table-format=6.0] S[table-format=5.0] S[table-format=1.3] S[table-format=1.3] S[table-format=1.3]@{}} | |
| 584 | 592 | \toprule |
| 585 | −Scénario & {Taux (\%)} & {Service de la dette (\$/an)} & {Flux après dette, an 6 (\$)} & {RCD an 6} & {RCD an 8} & {RCD an 10} \\ | |
| 593 | +Scénario & {Taux (\%)} & {Service dette (\$/an)} & {Flux ap. dette an 6 (\$)} & {RCD an 6} & {RCD an 8} & {RCD an 10} \\ | |
| 586 | 594 | \midrule |
| 587 | 595 | Taux inchangé & 4.12 & 74963 & 55927 & 1.746 & 1.894 & 2.049 \\ |
| 588 | 596 | +100 points de base & 5.12 & 85177 & 45713 & 1.537 & 1.667 & 1.803 \\ |
@@ -594,8 +602,9 @@ Taux inchangé & 4.12 & 74963 & 55927 & 1.746 & 1.894 & 2.049 \\ | ||
| 594 | 602 | \end{table} |
| 595 | 603 | |
| 596 | 604 | \begin{figure}[htbp]\centering |
| 597 | −\includegraphics{ch47/ch47-fig-rcd-scenarios.pdf} | |
| 605 | +\includegraphics[width=\linewidth]{ch47/ch47-fig-rcd-scenarios.pdf} | |
| 598 | 606 | \caption{Ratio de couverture de la dette par année de projection : pendant le premier terme, le ratio part du seuil de 1,20 et monte avec le revenu net ; au renouvellement de l'année 6, une hausse de 300~points de base le ramène tout juste au-dessus de 1,20.} |
| 607 | +\label{fig:47:rcd-scenarios} | |
| 599 | 608 | \figsource{\synth{script \texttt{ch47\_dossier\_figures.py}, graine 20261031.}} |
| 600 | 609 | \end{figure} |
| 601 | 610 | |
@@ -605,14 +614,14 @@ Le programme APH Select de la SCHL, en vigueur depuis mars 2022, module les cond | ||
| 605 | 614 | |
| 606 | 615 | \begin{table}[htbp]\centering |
| 607 | 616 | \caption{Variante APH Select (50 points : RPV \pct{85}, RCD 1,10) sur la valeur finale de \dollars{2060000}, taux \pct{4.12}, trois amortissements (données synthétiques).}\label{tab:47:aph} |
| 608 | −\small\begin{tabular}{@{}l S[table-format=7.0] S[table-format=7.0] S[table-format=2.1] S[table-format=5.0] S[table-format=6.0] S[table-format=1.2] S[table-format=6.0]@{}} | |
| 617 | +\footnotesize\setlength{\tabcolsep}{3pt}\begin{tabular}{@{}L{3.1cm} S[table-format=7.0] S[table-format=2.1] S[table-format=5.0] S[table-format=6.0] S[table-format=1.2] S[table-format=6.0]@{}} | |
| 609 | 618 | \toprule |
| 610 | −Amortissement & {Prêt max. RPV (\$)} & {Prêt max. RCD (\$)} & {RPV effectif (\%)} & {Service dette (\$)} & {Mise de fonds (\$)} & {Prime (\%)} & {Prime (\$)} \\ | |
| 619 | +Formule & {Prêt (\$)} & {RPV (\%)} & {Serv. dette (\$)} & {Mise de fonds (\$)} & {Prime (\%)} & {Prime (\$)} \\ | |
| 611 | 620 | \midrule |
| 612 | −Conventionnel 40 ans (RPV 75 \%, RCD 1,20) & 1545000 & 1475959 & 71.6 & 74963 & 584041 & {—} & {—} \\ | |
| 613 | −APH Select 40 ans & 1751000 & 1610138 & 78.2 & 81778 & 449862 & 5.10 & 82117 \\ | |
| 614 | −APH Select 45 ans & 1751000 & 1682397 & 81.7 & 81778 & 377603 & 6.35 & 106832 \\ | |
| 615 | −APH Select 50 ans & 1751000 & 1741328 & 84.5 & 81778 & 318672 & 6.60 & 114928 \\ | |
| 621 | +Conventionnel 40 ans (RPV 75 \%, RCD 1,20 ; prêt RPV 1~545~000) & 1475959 & 71.6 & 74963 & 584041 & {—} & {—} \\ | |
| 622 | +APH Select 40 ans (prêt RPV 1~751~000) & 1610138 & 78.2 & 81778 & 449862 & 5.10 & 82117 \\ | |
| 623 | +APH Select 45 ans & 1682397 & 81.7 & 81778 & 377603 & 6.35 & 106832 \\ | |
| 624 | +APH Select 50 ans & 1741328 & 84.5 & 81778 & 318672 & 6.60 & 114928 \\ | |
| 616 | 625 | \bottomrule |
| 617 | 626 | \end{tabular} |
| 618 | 627 | \tabsource{\synth{paramètres \textcite{schlaphselect,schlaphprimes} ; calculs \texttt{ch47\_dossier.py}, fonction \texttt{aph\_select} ; admissibilité au programme non vérifiée.}} |
@@ -622,11 +631,11 @@ APH Select 50 ans & 1751000 & 1741328 & 84.5 & 81778 & 318672 & 6.60 & 114928 \\ | ||
| 622 | 631 | |
| 623 | 632 | \subsection{Lois des intrants} |
| 624 | 633 | |
| 625 | −L'analyse de sensibilité un facteur à la fois ne dit pas quelle est la probabilité que plusieurs facteurs se dégradent ensemble. La simulation de Monte-Carlo, introduite en évaluation immobilière par \textcite{pyhrr1973} et \textcite{wofford1978} et développée par \textcite{frenchGabrielli2005} et \textcite{hoesliJaniBender2006}, tire conjointement les intrants incertains du modèle de flux dans des lois déclarées, recalcule la valeur pour chaque tirage et décrit la distribution obtenue. Le \chref{40} en a exposé la méthode ; nous l'appliquons au dossier avec \num{10000} tirages et la graine \texttt{20261031}, ce qui rend chaque chiffre reproductible. Le \cref{tab:47:lois} déclare les sept lois. Les choix suivent trois principes : centrer chaque loi sur l'hypothèse de base, pour que la simulation mesure l'incertitude autour du scénario déterministe et non un autre scénario ; donner à chaque loi une dispersion justifiée par les données disponibles ; lier le taux terminal au taux d'actualisation par un écart aléatoire plutôt que de les tirer indépendamment, parce que les deux réagissent aux mêmes mouvements des marchés de capitaux. L'inoccupation suit une loi log-normale de médiane \pct{3.9} et d'écart-type logarithmique \num{0.30}, ce qui place \pct{90} des tirages entre \pct{2.4} et \pct{6.3}, plage qui couvre les observations SCHL de 2023 à 2025 et les prévisions à 2028. La croissance des loyers de marché est normale de moyenne \pct{3.5} et d'écart-type un point, soit un intervalle à \pct{90} de \pct{1.8} à \pct{5.1}. La rotation est triangulaire entre \pct{8} et \pct{25} avec un mode à \pct{15} : la borne basse correspond au taux régional de la SCHL, la borne haute à un immeuble où plusieurs départs se concentrent. La croissance des dépenses est normale (\pct{3.0}, écart-type \num{0.75} point), le taux d'actualisation normal (\pct{7.25}, écart-type 50~points de base) et l'écart terminal $r - R_T$ normal (\num{2.25} points, écart-type 40~points de base), avec un plancher de \pct{3.5} pour le taux terminal. Chaque dépense en capital est multipliée par un facteur log-normal de médiane 1 et d'écart-type logarithmique \num{0.20}, indépendant d'un chantier à l'autre. La croissance des loyers contractuels (\pct{3.1}) est maintenue fixe : c'est un paramètre réglementaire dont la variabilité d'une année à l'autre se compense sur dix ans. | |
| 634 | +L'analyse de sensibilité un facteur à la fois ne dit pas quelle est la probabilité que plusieurs facteurs se dégradent ensemble. La simulation de Monte-Carlo, introduite en évaluation immobilière par \textcite{pyhrr1973} et \textcite{wofford1978} et développée par \textcite{frenchGabrielli2005} et \textcite{hoesliJaniBender2006}, tire conjointement les intrants incertains du modèle de flux dans des lois déclarées, recalcule la valeur pour chaque tirage et décrit la distribution obtenue. Le \cref{ch:40} en a exposé la méthode ; nous l'appliquons au dossier avec \num{10000} tirages et la graine \texttt{20261031}, ce qui rend chaque chiffre reproductible. Le \cref{tab:47:lois} déclare les sept lois. Les choix suivent trois principes : centrer chaque loi sur l'hypothèse de base, pour que la simulation mesure l'incertitude autour du scénario déterministe et non un autre scénario ; donner à chaque loi une dispersion justifiée par les données disponibles ; lier le taux terminal au taux d'actualisation par un écart aléatoire plutôt que de les tirer indépendamment, parce que les deux réagissent aux mêmes mouvements des marchés de capitaux. L'inoccupation suit une loi log-normale de médiane \pct{3.9} et d'écart-type logarithmique \num{0.30}, ce qui place \pct{90} des tirages entre \pct{2.4} et \pct{6.3}, plage qui couvre les observations SCHL de 2023 à 2025 et les prévisions à 2028. La croissance des loyers de marché est normale de moyenne \pct{3.5} et d'écart-type un point, soit un intervalle à \pct{90} de \pct{1.8} à \pct{5.1}. La rotation est triangulaire entre \pct{8} et \pct{25} avec un mode à \pct{15} : la borne basse correspond au taux régional de la SCHL, la borne haute à un immeuble où plusieurs départs se concentrent. La croissance des dépenses est normale (\pct{3.0}, écart-type \num{0.75} point), le taux d'actualisation normal (\pct{7.25}, écart-type 50~points de base) et l'écart terminal $r - R_T$ normal (\num{2.25} points, écart-type 40~points de base), avec un plancher de \pct{3.5} pour le taux terminal. Chaque dépense en capital est multipliée par un facteur log-normal de médiane 1 et d'écart-type logarithmique \num{0.20}, indépendant d'un chantier à l'autre. La croissance des loyers contractuels (\pct{3.1}) est maintenue fixe : c'est un paramètre réglementaire dont la variabilité d'une année à l'autre se compense sur dix ans. | |
| 626 | 635 | |
| 627 | 636 | \begin{table}[htbp]\centering |
| 628 | 637 | \caption{Lois déclarées des intrants de la simulation (10~000 tirages, graine 20261031) et quantiles effectivement tirés.}\label{tab:47:lois} |
| 629 | −\footnotesize\begin{tabular}{@{}L{3.6cm}L{4.5cm}S[table-format=1.4]S[table-format=1.4]S[table-format=1.4]@{}} | |
| 638 | +\footnotesize\setlength{\tabcolsep}{3pt}\begin{tabular}{@{}L{3.2cm}L{4.1cm}S[table-format=1.4]S[table-format=1.4]S[table-format=1.4]@{}} | |
| 630 | 639 | \toprule |
| 631 | 640 | Intrant & Loi (paramètres) & {Moyenne tirée} & {Quantile 5 \%} & {Quantile 95 \%} \\ |
| 632 | 641 | \midrule |
@@ -654,14 +663,15 @@ le code vectorisé reproduisant exactement, pour les intrants de base, la projec | ||
| 654 | 663 | La \cref{fig:47:montecarlo} et le \cref{tab:47:mc-resultats} présentent les résultats. La valeur simulée a une médiane de \dollars{2191608}, très proche de la valeur déterministe de \dollars{2208566}, et une moyenne de \dollars{2233506}, supérieure parce que la valeur est une fonction convexe du taux terminal et du taux d'actualisation ; \textcite{hoesliJaniBender2006} signalent ce biais de convexité, qui justifie de rapporter la médiane plutôt que la moyenne comme résumé central. L'écart-type est de \dollars{350443}, soit un coefficient de variation de \pct{15.7} ; l'intervalle à \pct{90} s'étend de \dollars{1735194} à \dollars{2876902}, c'est-à-dire de $-\pct{21}$ à $+\pct{30}$ autour de la médiane. La probabilité que la valeur simulée soit inférieure à l'indication par capitalisation directe est de \pct{30.9}, et celle qu'elle soit inférieure à deux millions de dollars de \pct{26.5}. Ces résultats placent l'incertitude de la méthode des flux actualisés dans son vrai ordre de grandeur : une plage de plus ou moins \pct{25} n'est pas un défaut du modèle mais la mesure honnête de ce qu'un horizon de dix ans et une valeur terminale à \pct{70} impliquent. |
| 655 | 664 | |
| 656 | 665 | \begin{figure}[htbp]\centering |
| 657 | −\includegraphics{ch47/ch47-fig-montecarlo.pdf} | |
| 666 | +\includegraphics[width=\linewidth]{ch47/ch47-fig-montecarlo.pdf} | |
| 658 | 667 | \caption{Distribution de la valeur sur \num{10000} tirages : quantiles 5~\%, 50~\% et 95~\% (traits tiretés), valeur DCF déterministe et indication par capitalisation directe. La distribution est asymétrique à droite, effet de la convexité de la valeur dans les taux.} |
| 668 | +\label{fig:47:montecarlo} | |
| 659 | 669 | \figsource{\synth{script \texttt{ch47\_dossier\_figures.py}, graine 20261031.}} |
| 660 | 670 | \end{figure} |
| 661 | 671 | |
| 662 | 672 | \begin{table}[htbp]\centering |
| 663 | 673 | \caption{Résultats de la simulation : valeur, ratios de couverture et corrélations de rang (données synthétiques).}\label{tab:47:mc-resultats} |
| 664 | −\small\begin{tabular}{@{}L{8.2cm}S[table-format=7.0]@{}} | |
| 674 | +\small\begin{tabular}{@{}L{7.3cm}S[table-format=7.0]@{}} | |
| 665 | 675 | \toprule |
| 666 | 676 | Statistique & {Valeur} \\ |
| 667 | 677 | \midrule |
@@ -698,8 +708,9 @@ Corrélation de rang avec la valeur : taux terminal & {$-0{,}78$} \\ | ||
| 698 | 708 | La \cref{fig:47:tornade} fait varier chaque intrant, isolément, entre ses quantiles 10 et 90, les autres restant à leur valeur de base. Le taux d'actualisation domine (valeur de \dollars{1931796} à \dollars{2568330}, amplitude \dollars{636535}), suivi de la croissance des loyers de marché (amplitude \dollars{459632}) et de l'écart terminal (\dollars{320404}) ; la croissance des dépenses (\dollars{185540}), l'inoccupation (\dollars{117049}), le facteur CAPEX (\dollars{92066}) et la rotation (\dollars{90738}) viennent loin derrière. Les corrélations de rang de la simulation conjointe confirment cette hiérarchie : $-0{,}78$ pour le taux terminal, $-0{,}71$ pour le taux d'actualisation, $+0{,}49$ pour la croissance des loyers de marché. La leçon est double. D'une part, l'incertitude de la valeur est d'abord une incertitude sur les taux, c'est-à-dire sur le marché des capitaux et sur la prime de risque exigée, et non sur l'exploitation de l'immeuble : c'est ce que \textcite{sivitanidouSivitanides1999} et \textcite{chervachidzeWheaton2013} ont établi empiriquement pour les taux de capitalisation. D'autre part, la rotation, dont dépend pourtant tout le rattrapage des loyers, pèse peu sur la valeur parce que ses effets s'exercent sur les premières années, fortement actualisées, et que la croissance des loyers de marché gouverne de toute façon le revenu de l'année 11 qui fonde la valeur terminale ; l'écart entre capitalisation et DCF constaté plus haut tient moins à la rotation qu'au couple de taux retenu. |
| 699 | 709 | |
| 700 | 710 | \begin{figure}[htbp]\centering |
| 701 | −\includegraphics{ch47/ch47-fig-tornade.pdf} | |
| 711 | +\includegraphics[width=\linewidth]{ch47/ch47-fig-tornade.pdf} | |
| 702 | 712 | \caption{Diagramme en tornade : valeur DCF lorsque chaque intrant est porté, seul, à son quantile 10~\% puis 90~\%, les autres restant à la base (trait vertical, \dollars{2208566}). Les taux dominent ; la rotation et les dépenses en capital sont des risques de second ordre pour la valeur.} |
| 713 | +\label{fig:47:tornade} | |
| 703 | 714 | \figsource{\synth{script \texttt{ch47\_dossier\_figures.py}, graine 20261031.}} |
| 704 | 715 | \end{figure} |
| 705 | 716 | |
@@ -711,13 +722,13 @@ La lecture du ratio de couverture renverse partiellement la hiérarchie. Le prê | ||
| 711 | 722 | |
| 712 | 723 | \subsection{Pondération des indications} |
| 713 | 724 | |
| 714 | −Le dossier a produit trois indications de valeur et deux repères (\cref{tab:47:reconciliation}). La réconciliation, au sens des Normes et du \chref{20}, n'est pas une moyenne : c'est un jugement motivé sur la fiabilité relative de chaque indication compte tenu de la qualité des données, de la pertinence de la méthode pour le type de bien et pour le marché, et du droit évalué. | |
| 725 | +Le dossier a produit trois indications de valeur et deux repères (\cref{tab:47:reconciliation}). La réconciliation, au sens des Normes et du \cref{ch:20}, n'est pas une moyenne : c'est un jugement motivé sur la fiabilité relative de chaque indication compte tenu de la qualité des données, de la pertinence de la méthode pour le type de bien et pour le marché, et du droit évalué. | |
| 715 | 726 | |
| 716 | 727 | La capitalisation directe repose sur quarante ventes, sur un revenu net normalisé poste par poste et sur une médiane de taux stable dans le temps et cohérente avec les fourchettes publiées ; elle est la méthode que les acheteurs et les prêteurs de ce segment utilisent effectivement. Sa faiblesse est de résumer en un taux l'anticipation de rattrapage des loyers, sans que l'on sache si le comparable moyen partage le profil du sujet. Elle reçoit le poids le plus élevé, \pct{45}. La méthode des flux actualisés explicite la trajectoire des loyers, les dépenses en capital et la revente ; elle est la seule à intégrer les trois chantiers et le profil des baux. Sa faiblesse est la sensibilité de \pct{70} de sa valeur à un couple de taux terminal et d'actualisation que le marché ne révèle pas directement, et la dépendance à une hypothèse de rotation plus favorable que les données régionales ; la simulation a montré un intervalle à \pct{90} de plus ou moins un quart. Elle reçoit \pct{35}. La comparaison par unité s'appuie sur dix ventes ajustées par des coefficients estimés avec une précision modeste et sur un ajustement de loyer qui, à lui seul, atteint souvent \pct{20} du prix ; elle est la méthode la moins fiable ici, mais elle ancre la valeur dans les prix payés par logement et elle rappelle que le marché ne paie pas la totalité du rattrapage futur. Elle reçoit \pct{20}. Le multiplicateur du revenu brut n'est pas pondéré ; il situe la valeur entre \num{11.5} et \num{14.6} fois le revenu brut, soit entre \dollars{1.90} et \dollars{2.42} millions, plage qui contient les trois indications. |
| 717 | 728 | |
| 718 | 729 | \begin{table}[htbp]\centering |
| 719 | 730 | \caption{Réconciliation des indications de valeur (données synthétiques).}\label{tab:47:reconciliation} |
| 720 | −\small\begin{tabular}{@{}L{6.0cm}S[table-format=7.0]S[table-format=2.0]S[table-format=7.0]@{}} | |
| 731 | +\small\begin{tabular}{@{}L{5.8cm}S[table-format=7.0]S[table-format=3.0]S[table-format=7.0]@{}} | |
| 721 | 732 | \toprule |
| 722 | 733 | Indication & {Valeur (\$)} & {Poids (\%)} & {Contribution (\$)} \\ |
| 723 | 734 | \midrule |
@@ -749,7 +760,7 @@ est arrondie à \dollars{2060000}, l'arrondi au dix mille près reflétant la pr | ||
| 749 | 760 | |
| 750 | 761 | \subsection{Valeur, rôle et droits de mutation} |
| 751 | 762 | |
| 752 | −La valeur finale est inférieure de \pct{6.4} à la valeur inscrite au rôle 2026-2028 (\dollars{2200000}), ce qui pourrait surprendre dans un marché où les valeurs au rôle sont réputées en retard sur le marché. Deux raisons l'expliquent et illustrent la différence de base de valeur. D'abord, le rôle évalue l'immeuble comme si le vendeur détenait tous les droits du locataire \parencite[art.~45.1]{lfm}, c'est-à-dire aux loyers de marché : en reprenant le calcul du dossier avec les loyers de marché, le revenu brut effectif serait de \dollars{188141}, le revenu net de \dollars{108865} (\dollars{18909} de plus) et la valeur par capitalisation de \dollars{2465788} ; la valeur au rôle, à \pct{89} de ce chiffre, est alors cohérente avec un marché qui a progressé depuis le 1\textsuperscript{er} juillet 2024. Ensuite, la date de référence du rôle précède de vingt-six mois la date d'évaluation ; l'écart entre les deux dates se mesure par les indices et non par comparaison directe. L'évaluateur ne doit donc ni « corriger » sa valeur pour la rapprocher du rôle, ni conclure que le rôle est erroné : les deux chiffres répondent à deux questions différentes, et l'écart de \pct{6} s'explique entièrement par le droit évalué. Un propriétaire qui jugerait sa valeur au rôle excessive disposerait du recours en révision du \chref{9}, mais l'argument des baux à loyer bas lui serait fermé par l'article~45.1. | |
| 763 | +La valeur finale est inférieure de \pct{6.4} à la valeur inscrite au rôle 2026-2028 (\dollars{2200000}), ce qui pourrait surprendre dans un marché où les valeurs au rôle sont réputées en retard sur le marché. Deux raisons l'expliquent et illustrent la différence de base de valeur. D'abord, le rôle évalue l'immeuble comme si le vendeur détenait tous les droits du locataire \parencite[art.~45.1]{lfm}, c'est-à-dire aux loyers de marché : en reprenant le calcul du dossier avec les loyers de marché, le revenu brut effectif serait de \dollars{188141}, le revenu net de \dollars{108865} (\dollars{18909} de plus) et la valeur par capitalisation de \dollars{2465788} ; la valeur au rôle, à \pct{89} de ce chiffre, est alors cohérente avec un marché qui a progressé depuis le 1\textsuperscript{er} juillet 2024. Ensuite, la date de référence du rôle précède de vingt-six mois la date d'évaluation ; l'écart entre les deux dates se mesure par les indices et non par comparaison directe. L'évaluateur ne doit donc ni « corriger » sa valeur pour la rapprocher du rôle, ni conclure que le rôle est erroné : les deux chiffres répondent à deux questions différentes, et l'écart de \pct{6} s'explique entièrement par le droit évalué. Un propriétaire qui jugerait sa valeur au rôle excessive disposerait du recours en révision du \cref{ch:09}, mais l'argument des baux à loyer bas lui serait fermé par l'article~45.1. | |
| 753 | 764 | |
| 754 | 765 | Enfin, pour l'acheteur, l'acquisition au prix de \dollars{2060000} entraîne des droits sur les mutations immobilières de \dollars{41325.50} selon le barème montréalais de 2026 (\pct{0.5} sur \dollars{62900}, \pct{1} jusqu'à \dollars{315000}, \pct{1.5} jusqu'à \dollars{552300}, \pct{2} jusqu'à \dollars{1104700}, \pct{2.5} au-delà) \parencite{mtlmutations2026}. Au financement conventionnel recalculé à la valeur finale, le prêt reste limité par la couverture à \dollars{1475959} (RPV \pct{71.6}), la mise de fonds est de \dollars{584041} et le besoin de liquidités à la signature, droits compris et avant frais de notaire et d'inspection, de \dollars{625366}. Le rendement courant des fonds propres passe à \pct{2.57} et le TRI des fonds propres sur dix ans à \pct{13.83} (contre \pct{8.11} sans levier à ce prix), le levier demeurant négatif sur le revenu courant et positif sur le rendement total. |
| 755 | 766 | |
added
references/lib-ch47.bib
+8 −0
@@ -0,0 +1,8 @@ | ||
| 1 | +% lib-ch47.bib — chapitre 47 : AUCUNE nouvelle entrée. Toutes les citations du chapitre utilisent des clés existantes de references/library.bib : | |
| 2 | +% oeaqNormes, ivs2025, cuspap2026, deontOEAQ, ccq, loi31_2024, loi65_2024, reglCriteresFixation, decret1455_2025, talIndices2026, talCommunique2026, | |
| 3 | +% talResumesDecisions, loiTAL, lau, lfm, ldmi, mtlmutations2026, montrealrole2026, mtlrole2026, ledevoirmtl2026, mtltaux2026, taxescolaire2026, | |
| 4 | +% schlLocatif2025, schlrmltables2025, schlpmh2026ete, apciqt32026, apciqbaromontreal2026, cbrecaprates2026q3, altusmontreal2026q2, bdc20261028, bdcvalet, | |
| 5 | +% bdctauxhypothecaires2026, statcanipc, teranet202609, osfiMqr, schlaphselect, schlaphprimes, schlfraisexploitation2015, oaciqFormulaireDV, oaciqContexteDV, | |
| 6 | +% geltnerMillerClaytonEichholtz2014, brueggemanFisher2022, fisherMartin2007, appraisal2026, gordonShapiro1956, hoesliJaniBender2006, frenchGabrielli2005, | |
| 7 | +% pyhrr1973, wofford1978, sivitanidouSivitanides1999, chervachidzeWheaton2013, ducaLing2020, bokhariGeltner2018, arnott1995, kholodilin2024, | |
| 8 | +% diamondMcquadeQian2019, autorPalmerPathak2014, dubeTheriaultDesrosiers2013. | |
modified
solutions/ch47.tex
+30 −1
@@ -1 +1,30 @@ | ||
| 1 | −% Solutions du chapitre 47 — Cas 3 : immeuble de 12 logements à Montréal | |
| 1 | +% Solutions du chapitre 47 — Cas 3 : immeuble de 12 logements à Montréal (données synthétiques) | |
| 2 | +% Tous les chiffres proviennent de analysis/python/cases/ch47_dossier.py (fonction exercices) ; tests : analysis/python/tests/test_ch47.py | |
| 3 | + | |
| 4 | +\begin{solution}{ex:47:1}{Vacance économique de 5~\%} | |
| 5 | +Le revenu brut effectif devient $RBE = \dollars{165540} \times (1 - \num{0.05}) + \dollars{9360} = \dollars{166623.00}$, en baisse de \dollars{1820.94}. La gestion imputée, à \pct{4} du revenu brut effectif, diminue de \dollars{72.84}, de sorte que les dépenses hors réserve passent de \dollars{70385.19} à \dollars{70312.35} ; la réserve reste à \dollars{8103}. Le revenu net normalisé est $\NOI_1 = \dollars{166623.00} - \dollars{70312.35} - \dollars{8103.00} = \dollars{88207.65}$, soit \dollars{1748.10} de moins que dans le dossier (la baisse du revenu est partiellement compensée par la baisse de la gestion). Capitalisé à \pct{4.415}, il donne $\dollars{88207.65} / \num{0.04415} = \dollars{1997908.27}$ : la valeur diminue de \dollars{39594.62}, soit \pct{1.9}. On retient qu'un point d'inoccupation vaut, pour cet immeuble, environ \dollars{36000} de valeur, et que la sensibilité à la vacance est modérée parce que la provision ne s'applique qu'aux loyers et non aux autres revenus. | |
| 6 | +\end{solution} | |
| 7 | + | |
| 8 | +\begin{solution}{ex:47:2}{Taxes municipales indexées plutôt que recalculées} | |
| 9 | +Le compte 2025 indexé de \pct{5.3} donne $\dollars{10631} \times \num{1.053} = \dollars{11194.44}$, contre \dollars{10188.20} par recalcul au taux général sur la valeur au rôle : l'écart est de \dollars{1006.24} de dépenses supplémentaires. Le revenu net passe de \dollars{89955.75} à \dollars{88949.51} et l'indication par capitalisation directe à $\dollars{88949.51} / \num{0.04415} = \dollars{2014711.42}$, soit \dollars{22791.46} de moins (\pct{1.1}). L'évaluateur doit signaler ce choix parce que les deux méthodes de normalisation reposent sur des informations incomplètes : le recalcul applique le seul taux général et ignore la taxe d'arrondissement et un éventuel taux propre aux immeubles de six logements et plus, tandis que l'indexation suppose que la hausse moyenne annoncée pour l'arrondissement s'applique à l'immeuble. La règle 2.3 des Normes exige que l'étendue du travail et les réserves soient décrites ; l'écart de \dollars{1006} sur les dépenses, qui se traduit par \dollars{22791} de valeur, est précisément le type d'incertitude qu'un lecteur du rapport doit pouvoir mesurer. La bonne pratique est d'obtenir le compte de taxes 2026 réel et le document officiel des taux avant la remise du rapport. | |
| 10 | +\end{solution} | |
| 11 | + | |
| 12 | +\begin{solution}{ex:47:3}{Choix du taux de capitalisation} | |
| 13 | +Avec $\NOI_1 = \dollars{89955.75}$ : au premier quartile (\pct{4.1525}), $V = \dollars{2166303.49}$ (+\pct{6.3} par rapport à la médiane) ; au troisième quartile (\pct{4.6375}), $V = \dollars{1939746.68}$ ($-\pct{4.8}$) ; au taux prédit par la régression pour un immeuble d'état 3 de douze logements, soit la constante \pct{4.567}, $V = \dollars{1969693.82}$ ($-\pct{3.3}$) ; au taux moyen pondéré par le revenu net (\pct{4.332}), $V = \dollars{2076580.69}$ (+\pct{1.9}). La fourchette interquartile mesure la dispersion des comparables et constitue une borne d'incertitude raisonnable de la méthode. Le taux de régression est pertinent parce qu'il corrige la taille, mais il repose sur une pente estimée imprécisément et sur une constante qui, par construction, s'approche de la moyenne de l'échantillon pour un immeuble moyen ; il suggère néanmoins qu'un douze logements se vend à un taux légèrement supérieur à la médiane de l'ensemble, ce qui plaide pour la prudence. La moyenne pondérée par le revenu net donne un poids excessif aux grands immeubles du Plateau et d'Ahuntsic, dont le taux est plus bas et le profil différent du sujet : elle est la statistique la moins pertinente ici. La médiane reste le meilleur compromis, la réconciliation finale (taux implicite \pct{4.37}) se situant d'ailleurs entre la médiane et la moyenne pondérée. | |
| 14 | +\end{solution} | |
| 15 | + | |
| 16 | +\begin{solution}{ex:47:4}{Rotation des locataires} | |
| 17 | +Avec une rotation de \pct{10} (un logement relocalisé par an), la valeur DCF est de \dollars{2140916.43}, soit \dollars{67649.48} de moins que la base ($-\pct{3.1}$) ; le revenu net de l'année 11 est de \dollars{156352.52} et le revenu brut potentiel de l'année 10 de \dollars{247211.89}. Avec une rotation de \pct{25} (trois logements par an), la valeur est de \dollars{2231654.72}, soit \dollars{23088.81} de plus (+\pct{1.0}) ; le revenu net de l'année 11 atteint \dollars{160590.16} et le revenu brut de l'année 10 \dollars{251977.55}. L'asymétrie vient de ce que le rattrapage est borné par le marché : à \pct{15}, l'écart de \pct{11} est déjà résorbé vers la cinquième année et le revenu brut de l'année 10 atteint \pct{99} du marché ; accélérer la rotation ne peut gagner que quelques années d'écart sur les premiers exercices, alors que la ralentir repousse le rattrapage au-delà de l'horizon et ampute durablement le revenu, y compris celui de l'année 11 qui fonde la valeur terminale. Ces montants sont exactement les bornes de la barre « rotation annuelle » de la tornade (quantiles 10 et 90 de la loi triangulaire, soit \pct{11.45} et \pct{20.88}, qui donnent \dollars{2140916} et \dollars{2231655} parce que l'arrondi à l'entier du nombre de logements relocalisés ramène ces taux à un et trois logements respectivement) ; l'amplitude de \dollars{90738} place la rotation au dernier rang des sources d'incertitude. | |
| 18 | +\end{solution} | |
| 19 | + | |
| 20 | +\begin{solution}{ex:47:5}{Hausse du taux hypothécaire de 100~points de base} | |
| 21 | +À \pct{5.12} composé semestriellement, le taux mensuel équivalent est $(1 + \num{0.0256})^{1/6} - 1 = \num{0.0042207}$ et la constante hypothécaire sur 480 mois $f = \pct{5.8391}$. Le prêt maximal selon le ratio prêt-valeur de \pct{75} sur \dollars{2060000} est \dollars{1545000} ; selon la couverture de \num{1.20}, $\dollars{89955.75} / (\num{1.20} \times \num{0.058391}) = \dollars{1283814.77}$. La contrainte liante demeure la couverture ; le prêt est de \dollars{1283814.77}, le ratio prêt-valeur effectif de \pct{62.3}, le service de la dette inchangé à \dollars{74963.13} (il est fixé par le revenu net et le ratio de couverture, non par le taux) et la mise de fonds de \dollars{776185.23}. Par rapport au cas de base à la valeur finale (prêt de \dollars{1475959.45}), le prêt diminue de \dollars{192144.68}, soit \pct{13.0} : une hausse de 100~points de base du taux réduit la capacité d'emprunt d'un immeuble contraint par la couverture dans la même proportion que la constante hypothécaire augmente ($\num{0.050789} / \num{0.058391} - 1 = -\pct{13.0}$). La mise de fonds requise augmente de \dollars{192145}, ce qui, à rendement exigé constant, se répercute sur le prix que les acheteurs endettés peuvent offrir : c'est le canal par lequel les taux d'intérêt agissent sur les taux de capitalisation. | |
| 22 | +\end{solution} | |
| 23 | + | |
| 24 | +\begin{solution}{ex:47:6}{Variante APH Select} | |
| 25 | +À la valeur finale de \dollars{2060000}, le prêt maximal selon le ratio prêt-valeur de \pct{85} est \dollars{1751000} ; selon la couverture de \num{1.10} à \pct{4.12} sur 480 mois, $\dollars{89955.75} / (\num{1.10} \times \num{0.050789}) = \dollars{1610137.58}$. La couverture est liante ; le prêt est de \dollars{1610137.58}, soit \pct{78.2} de la valeur, la mise de fonds de \dollars{449862.42} (contre \dollars{584040.55} au financement conventionnel, un gain de \dollars{134178}). La prime d'assurance est de \pct{4.35} pour un ratio prêt-valeur entre 75 et \pct{80}, majorée de \pct{0.25} par tranche de cinq ans d'amortissement au-delà de vingt-cinq ans, soit $3 \times \pct{0.25} = \pct{0.75}$ pour quarante ans : \pct{5.10} du prêt, soit \dollars{82117.02}, normalement capitalisée au prêt. Le service de la dette est de \dollars{81777.96} et le flux après dette de la première année de $\dollars{89955.75} - \dollars{81777.96} = \dollars{8177.79}$, soit un rendement courant de \pct{1.8} sur la mise de fonds. Quant à la compatibilité : l'obtention de 50~points par le volet abordabilité exige, pour un immeuble existant, qu'une part des logements (par exemple \pct{40}) demeure à un loyer inférieur ou égal à \pct{30} du revenu médian des locataires pendant dix ans. Or la valeur DCF du dossier repose sur le rattrapage des loyers au marché par la rotation, et la valeur finale en retient une partie. Un engagement d'abordabilité fige une fraction du revenu sous le marché : la valeur de l'immeuble \emph{sous cet engagement} est une valeur d'investissement, propre au propriétaire qui l'accepte en contrepartie d'un financement avantageux, et non la valeur marchande du dossier. Le prêteur qui finance sur la base d'APH Select devrait soit demander une évaluation sous l'hypothèse (extraordinaire, à déclarer) de l'engagement d'abordabilité, soit s'assurer que la couverture de \num{1.10} est calculée sur le revenu contraint ; dans le dossier, le revenu de première année est déjà celui des baux en place, de sorte que le prêt de \dollars{1610138} est soutenable à court terme, mais la croissance du revenu qui justifie le prix le serait moins. La valeur marchande n'est donc pas la bonne base pour mesurer le risque de ce prêt à long terme ; il faut une projection conforme à l'engagement. | |
| 26 | +\end{solution} | |
| 27 | + | |
| 28 | +\begin{solution}{ex:47:7}{Simulation dans un marché détendu} | |
| 29 | +Avec une inoccupation log-normale de médiane \pct{6} (écart-type logarithmique \num{0.30}), les autres lois inchangées, \num{10000} tirages et la graine \texttt{20261031}, la valeur simulée a une médiane de \dollars{2108707} (contre \dollars{2191608} dans le dossier, $-\pct{3.8}$) et un intervalle 5--95~\% de \dollars{1654991} à \dollars{2785424}. Le ratio de couverture de la première année du prêt de \dollars{1475959} a une médiane de \num{1.156} ; la probabilité qu'il soit inférieur à 1,20 est de \pct{92.6} et celle qu'il soit inférieur à 1,00 de \pct{0.38} (38 tirages). Le prêt dimensionné sur une inoccupation de \pct{3.9} est donc en bris de covenant dans l'immense majorité des scénarios d'un marché détendu, bien que le défaut de paiement reste rare. Pour ramener la probabilité de bris sous \pct{10}, il faut que le service de la dette $SD'$ satisfasse $P(\NOI_1 / SD' < 1{,}20) = 0{,}10$, c'est-à-dire $SD' = q_{0{,}10}(\NOI_1) / 1{,}20$ où $q_{0{,}10}(\NOI_1) = \dollars{82228}$ est le quantile 10~\% du revenu net simulé ; $SD' = \dollars{68523}$, soit un prêt de $\dollars{68523} / \num{0.050789} = \dollars{1349000}$ environ et un ratio de couverture \emph{sur le revenu de base} de $\dollars{89955.75} / \dollars{68523} = \num{1.31}$. Le prêteur devrait donc exiger un ratio de couverture d'environ \num{1.31} (la vérification par simulation donne \pct{14.2} de bris à \num{1.30} et \pct{3.8} à \num{1.35}), contre \num{1.24} dans la loi de base du dossier : le covenant qui protège le prêteur dépend entièrement de la loi de l'inoccupation, ce qui justifie que celle-ci soit déclarée et discutée dans le rapport de consultation. | |
| 30 | +\end{solution} | |
modified
sources/registry.csv
+1 −0
@@ -291,3 +291,4 @@ SRC-E-066,rapport,Rapport mensuel sur le marché immobilier au Québec — août | ||
| 291 | 291 | SRC-E-067,article,Banque du Canada — décision du 28 octobre 2026 (analyse),Desjardins Études économiques,2026-10-28,https://www.desjardins.com/fr/epargne-placements/etudes-economiques/canada-bdc-28-octobre-2026.html,2026-10-31,verifie,ch. 3,"Secondaire (institution financière) ; confirme +25 pb à 2,50 %, première hausse depuis juillet 2023." |
| 292 | 292 | SRC-O-001,loi,Loi sur l'intérêt (L.R.C. 1985 ch. I-15) art. 6,Canada (ministère de la Justice),texte consolidé consulté le 2026-10-31,https://laws-lois.justice.gc.ca/fra/lois/i-15/page-1.html,2026-10-31,verifie,29;30;31;annexe conventions,"art. 6 lu : taux « calculé annuellement ou semestriellement, mais non d'avance » ; fonde la composition semestrielle de traite.finance.Hypotheque" |
| 293 | 293 | SRC-21-001,guide,RC4028 — Remboursement de la TPS/TVH pour habitations neuves,Agence du revenu du Canada,Rév. 10/2026,https://www.canada.ca/fr/agence-revenu/services/formulaires-publications/publications/rc4028/remboursement-tps-tvh-habitations-neuves.html,2026-10-31,partiellement_verifie,21,"Lu par agent web-fetch le 2026-10-31 : seuil 450 000 $ (maison construite par le propriétaire) verbatim ; remboursement premiers acheteurs (travaux dès le 20 mars 2025, 1 M$ / 1,5 M$) verbatim ; 36 % / 6 300 $ / 350 000 $ non vérifiés mot à mot. Pages Revenu Québec (TVQ) HTTP 403." |
| 294 | +# Aucune nouvelle source : le chapitre 47 ne cite que des clés déjà présentes dans references/library.bib (voir ch47.md).,,,,,,,,, | |
modified
verification/calculations.csv
+26 −0
@@ -52,3 +52,29 @@ CALC-30-10,30,"Proposition 30.1 : DCF = NOI_1/(r−g) pour T = 1, 5, 10, 25, 60 | ||
| 52 | 52 | CALC-30-11,30,"Sensibilité R_T 4,25 → 5,75 % : V 2 481 417 → 2 006 893 (+12,4 % → −9,1 %) ; surfaces V(r,R_T) et V(r,g) monotones",analysis/python/cases/ch30_dcf.py,analysis/python/tests/test_ch30.py::test_sensibilite_monotone,2481417 ; 2006893,2481416.97 ; 2006893.39,verifie,2026-10-31 |
| 53 | 53 | CALC-30-12,30,"Tornade : amplitude max = croissance des loyers de marché 368 235 ; scénarios : encadrement serré 1 912 046 (−13,4 %), hausse des taux 1 947 889 (−11,8 %) ; TRI FP si payé base 7,48 % / 8,31 % ; RCD min 1,04",analysis/python/cases/ch30_dcf.py,analysis/python/tests/test_ch30.py::test_tornade_et_scenarios,368235 ; 1912046 ; 1947889,368235.05 ; 1912046.35 ; 1947889.41,verifie,2026-10-31 |
| 54 | 54 | CALC-30-13,30,Solutions des exercices 30.1 à 30.8 (fonction exercices()),analysis/python/cases/ch30_dcf.py,analysis/python/tests/test_ch30.py::test_exercices_coherence,voir solutions/ch30.tex,OK,verifie,2026-10-31 |
| 55 | +CALC-47-001,47,RBP contractuel = 12 × somme des loyers,analysis/python/cases/ch47_dossier.py,analysis/python/tests/test_ch47.py::test_identites_revenus_depenses,165540,165540.00,verifie,2026-10-31 | |
| 56 | +CALC-47-002,47,RBE = RBP(1−0,039) + 9 360,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_identites_revenus_depenses,168443.94,168443.94,verifie | |
| 57 | +CALC-47-003,47,Dépenses normalisées hors réserve (taxes recalculées ; gestion 4 % RBE ; +3 %),analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees,70385.19,70385.19,verifie,2026-10-31 | |
| 58 | +CALC-47-004,47,RNE normalisé = RBE − DE − réserve 8 103,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees,89955.75,89955.75,verifie,2026-10-31 | |
| 59 | +CALC-47-005,47,Taux global : médiane de 40 ventes (moyenne 4,404 ; pondérée 4,332 ; Q1 4,1525 ; Q3 4,6375),analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees | |
| 60 | +CALC-47-006,47,Valeur par capitalisation directe RNE/0,04415,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_capitalisation_et_dcf,2037502.89,2037502.89,verifie | |
| 61 | +CALC-47-007,47,Projection DCF 10 ans (rotation 15 % ; g_c 3,1 ; g_m 3,5 ; g_d 3,0 ; CAPEX 85k/40k/120k) : RNE_11,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees,159658.38 | |
| 62 | +CALC-47-008,47,Valeur terminale brute RNE_11/0,05 et nette ×0,975,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_capitalisation_et_dcf,3193167.52 / 3113338.33,3193167.52 / 3113338.33 | |
| 63 | +CALC-47-009,47,Valeur DCF (r 7,25 %) = VA flux 662 409,91 + VA terminale 1 546 156,00 ; part terminale 70,0 %,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_capitalisation_et_dcf | |
| 64 | +CALC-47-010,47,OLS ln(prix/logement) : élasticité loyer 1,1707 ; taille 0,0079/log. ; R² 0,635,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees,1.1707 / 0.0079 | |
| 65 | +CALC-47-011,47,Comparaison par unité : prix/logement pondéré 155 908,56 × 12,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees,1870902.67,1870902.67,verifie | |
| 66 | +CALC-47-012,47,MRB médian des 10 comparables 11,94 × RBP contractuel,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees,1976547.60,1976547.60,verifie | |
| 67 | +CALC-47-013,47,Prêt max RCD 1,20 à 4,12 % / 480 mois (constante 5,0789 %) ; prêt RPV 75 % de V_dcf 1 656 424,43,analysis/python/traite/cas3.py,test_ch47.py::test_valeurs_citees | |
| 68 | +CALC-47-014,47,Service de la dette 74 963,13 ; paiement mensuel 6 246,93 ; solde après 60 mois 1 394 739,19 ; après 120 mois 1 295 148,22,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees | |
| 69 | +CALC-47-015,47,TRI fonds propres (achat à V_dcf) 11,22 % ; VAN nulle au TRI ; point mort 92,7 %,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_financement_identites,0.1122 / 0.927,0.1122 / 0.927 | |
| 70 | +CALC-47-016,47,Scénarios de renouvellement an 6 : SD +200 pb 95 920,20 ; RCD an 6 à +300 pb 1,222,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees,95920.20 / 1.222,95920.20 / 1.222 | |
| 71 | +CALC-47-017,47,APH Select 40 ans (RPV 85 %,RCD 1,10) à la valeur finale : prêt,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees,1610137.58,1610137.58 | |
| 72 | +CALC-47-018,47,Monte-Carlo 10 000 tirages graine 20261031 : médiane / q05 / q95 / moyenne / écart-type,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_monte_carlo_citations_et_reproductibilite,2191607.87 / 1735194.21 / 2876902.35 / 2233505.68 / 350442.57,idem,verifie,2026-10-31 | |
| 73 | +CALC-47-019,47,Monte-Carlo : P(RCD1<1,20) 0,4936 ; P(RCD1<1,00) 0 ; P(RCD6<1,20 | +200 pb) 0,0140 ; P(V<cap) 0,3094 ; P(V<rôle) 0 | |
| 74 | +CALC-47-020,47,Projection vectorisée (MC) reproduit cas3.projection_dcf aux paramètres de base (rtol 1e-12),analysis/python/cases/ch47_dossier.py,test_ch47.py::test_projection_vectorisee_reproduit_cas3,identique,identique,verifie,2026-10-31 | |
| 75 | +CALC-47-021,47,Tornade : amplitude taux d'actualisation 636 535 ; croissance loyers marché 459 632 ; écart terminal 320 404,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_projection_vectorisee_reproduit_cas3 (V_base),636534.56,636534.56,verifie,2026-10-31 | |
| 76 | +CALC-47-022,47,Réconciliation 45/35/20 → 2 064 054,90 arrondie à 2 060 000 ; ratio/rôle 0,9364 ; MRB 12,44 ; taux implicite 4,367 %,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees | |
| 77 | +CALC-47-023,47,Droits de mutation Montréal 2026 sur 2 060 000 (7 tranches),analysis/python/cases/ch47_dossier.py,test_ch47.py::test_droits_mutation_montreal,41325.50,41325.50,verifie,2026-10-31 | |
| 78 | +CALC-47-024,47,Valeur libre de baux (loyers de marché) : RNE 108 864,54 ; valeur 2 465 788,04,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees,2465788.04,2465788.04 | |
| 79 | +CALC-47-025,47,Financement à la valeur finale : mise de fonds 584 040,55 ; RPV 71,65 % ; TRI FP 13,83 % ; TRI sans levier 8,11 %,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees | |
| 80 | +CALC-47-026,47,Exercices 1 à 7 (RNE 88 207,65 ; 88 949,51 ; V Q1/Q3 ; rotation 10/25 % ; prêt à 5,12 % 1 283 814,77 ; APH ; MC inoccupation 6 % P=0,9262),analysis/python/cases/ch47_dossier.py | |
added
verification/chapters/ch47-calculations.csv
+27 −0
@@ -0,0 +1,27 @@ | ||
| 1 | +id,chapitre,description,script,test,resultat_attendu,resultat_obtenu,statut,date | |
| 2 | +CALC-47-001,47,RBP contractuel = 12 × somme des loyers,analysis/python/cases/ch47_dossier.py,analysis/python/tests/test_ch47.py::test_identites_revenus_depenses,165540,165540.00,verifie,2026-10-31 | |
| 3 | +CALC-47-002,47,RBE = RBP(1−0,039) + 9 360,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_identites_revenus_depenses,168443.94,168443.94,verifie,2026-10-31 | |
| 4 | +CALC-47-003,47,Dépenses normalisées hors réserve (taxes recalculées ; gestion 4 % RBE ; +3 %),analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees,70385.19,70385.19,verifie,2026-10-31 | |
| 5 | +CALC-47-004,47,RNE normalisé = RBE − DE − réserve 8 103,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees,89955.75,89955.75,verifie,2026-10-31 | |
| 6 | +CALC-47-005,47,Taux global : médiane de 40 ventes (moyenne 4,404 ; pondérée 4,332 ; Q1 4,1525 ; Q3 4,6375),analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees,0.04415,0.04415,verifie,2026-10-31 | |
| 7 | +CALC-47-006,47,Valeur par capitalisation directe RNE/0,04415,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_capitalisation_et_dcf,2037502.89,2037502.89,verifie,2026-10-31 | |
| 8 | +CALC-47-007,47,Projection DCF 10 ans (rotation 15 % ; g_c 3,1 ; g_m 3,5 ; g_d 3,0 ; CAPEX 85k/40k/120k) : RNE_11,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees,159658.38,159658.38,verifie,2026-10-31 | |
| 9 | +CALC-47-008,47,Valeur terminale brute RNE_11/0,05 et nette ×0,975,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_capitalisation_et_dcf,3193167.52 / 3113338.33,3193167.52 / 3113338.33,verifie,2026-10-31 | |
| 10 | +CALC-47-009,47,Valeur DCF (r 7,25 %) = VA flux 662 409,91 + VA terminale 1 546 156,00 ; part terminale 70,0 %,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_capitalisation_et_dcf,2208565.91,2208565.91,verifie,2026-10-31 | |
| 11 | +CALC-47-010,47,OLS ln(prix/logement) : élasticité loyer 1,1707 ; taille 0,0079/log. ; R² 0,635,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees,1.1707 / 0.0079,1.1707 / 0.0079,verifie,2026-10-31 | |
| 12 | +CALC-47-011,47,Comparaison par unité : prix/logement pondéré 155 908,56 × 12,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees,1870902.67,1870902.67,verifie,2026-10-31 | |
| 13 | +CALC-47-012,47,MRB médian des 10 comparables 11,94 × RBP contractuel,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees,1976547.60,1976547.60,verifie,2026-10-31 | |
| 14 | +CALC-47-013,47,Prêt max RCD 1,20 à 4,12 % / 480 mois (constante 5,0789 %) ; prêt RPV 75 % de V_dcf 1 656 424,43,analysis/python/traite/cas3.py,test_ch47.py::test_valeurs_citees,1475959.45,1475959.45,verifie,2026-10-31 | |
| 15 | +CALC-47-014,47,Service de la dette 74 963,13 ; paiement mensuel 6 246,93 ; solde après 60 mois 1 394 739,19 ; après 120 mois 1 295 148,22,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees,74963.13,74963.13,verifie,2026-10-31 | |
| 16 | +CALC-47-015,47,TRI fonds propres (achat à V_dcf) 11,22 % ; VAN nulle au TRI ; point mort 92,7 %,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_financement_identites,0.1122 / 0.927,0.1122 / 0.927,verifie,2026-10-31 | |
| 17 | +CALC-47-016,47,Scénarios de renouvellement an 6 : SD +200 pb 95 920,20 ; RCD an 6 à +300 pb 1,222,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees,95920.20 / 1.222,95920.20 / 1.222,verifie,2026-10-31 | |
| 18 | +CALC-47-017,47,APH Select 40 ans (RPV 85 %, RCD 1,10) à la valeur finale : prêt,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees,1610137.58,1610137.58,verifie,2026-10-31 | |
| 19 | +CALC-47-018,47,Monte-Carlo 10 000 tirages graine 20261031 : médiane / q05 / q95 / moyenne / écart-type,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_monte_carlo_citations_et_reproductibilite,2191607.87 / 1735194.21 / 2876902.35 / 2233505.68 / 350442.57,idem,verifie,2026-10-31 | |
| 20 | +CALC-47-019,47,Monte-Carlo : P(RCD1<1,20) 0,4936 ; P(RCD1<1,00) 0 ; P(RCD6<1,20 | +200 pb) 0,0140 ; P(V<cap) 0,3094 ; P(V<rôle) 0,5118,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_monte_carlo_citations_et_reproductibilite,0.4936 / 0 / 0.0140,idem,verifie,2026-10-31 | |
| 21 | +CALC-47-020,47,Projection vectorisée (MC) reproduit cas3.projection_dcf aux paramètres de base (rtol 1e-12),analysis/python/cases/ch47_dossier.py,test_ch47.py::test_projection_vectorisee_reproduit_cas3,identique,identique,verifie,2026-10-31 | |
| 22 | +CALC-47-021,47,Tornade : amplitude taux d'actualisation 636 535 ; croissance loyers marché 459 632 ; écart terminal 320 404,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_projection_vectorisee_reproduit_cas3 (V_base),636534.56,636534.56,verifie,2026-10-31 | |
| 23 | +CALC-47-022,47,Réconciliation 45/35/20 → 2 064 054,90 arrondie à 2 060 000 ; ratio/rôle 0,9364 ; MRB 12,44 ; taux implicite 4,367 %,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees,2060000,2060000,verifie,2026-10-31 | |
| 24 | +CALC-47-023,47,Droits de mutation Montréal 2026 sur 2 060 000 (7 tranches),analysis/python/cases/ch47_dossier.py,test_ch47.py::test_droits_mutation_montreal,41325.50,41325.50,verifie,2026-10-31 | |
| 25 | +CALC-47-024,47,Valeur libre de baux (loyers de marché) : RNE 108 864,54 ; valeur 2 465 788,04,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees,2465788.04,2465788.04,verifie,2026-10-31 | |
| 26 | +CALC-47-025,47,Financement à la valeur finale : mise de fonds 584 040,55 ; RPV 71,65 % ; TRI FP 13,83 % ; TRI sans levier 8,11 %,analysis/python/cases/ch47_dossier.py,test_ch47.py::test_valeurs_citees,584040.55,584040.55,verifie,2026-10-31 | |
| 27 | +CALC-47-026,47,Exercices 1 à 7 (RNE 88 207,65 ; 88 949,51 ; V Q1/Q3 ; rotation 10/25 % ; prêt à 5,12 % 1 283 814,77 ; APH ; MC inoccupation 6 % P=0,9262),analysis/python/cases/ch47_dossier.py,test_ch47.py::test_exercices,voir solutions/ch47.tex,conformes,verifie,2026-10-31 | |
added
verification/chapters/ch47-claims.csv
+38 −0
@@ -0,0 +1,38 @@ | ||
| 1 | +id,chapitre,affirmation,source_id,verification,date,statut,notes | |
| 2 | +CLM-47-001,47,Définition OEAQ de la valeur marchande (« prix sincère le plus probable… marché libre et ouvert à la concurrence ») et quatre conditions,SRC-C-001,NPP p. Intro-11 lues (notes-C §1.1),2026-10-31,verifie,citation ≤ 25 mots ; conditions paraphrasées | |
| 3 | +CLM-47-002,47,Contenu minimal du rapport écrit : 12 éléments (règle 2.3) ; inspection obligatoire (règle 2.4) ; hypothèses extraordinaires rappelées à chaque conclusion (2.1 c et 2.3 él. 7) ; rejet d'une méthode justifié,SRC-C-001,NPP lues (notes-C §2.1),2026-10-31,verifie, | |
| 4 | +CLM-47-003,47,Code de déontologie : n'agir que pour une partie (art. 20) ; divulgation double rôle (18-19) ; honoraires conditionnels interdits (24.1) ; signature et qualité (34),SRC-C-003,LégisQuébec C-26 r. 123 à jour au 2026-07-01 (notes-C §2.2),2026-10-31,verifie, | |
| 5 | +CLM-47-004,47,IVS 102 annexe A10 : définition de la market value (édition en vigueur 31 janv. 2025),SRC-C-014,PDF officiel (notes-C §1.2),2026-10-31,verifie, | |
| 6 | +CLM-47-005,47,Art. 1937 C.c.Q. : l'aliénation ne permet pas au nouveau locateur de résilier le bail ; 1936 maintien dans les lieux,SRC-A-001,LégisQuébec à jour au 2026-10-15 (notes-A §4),2026-10-31,verifie, | |
| 7 | +CLM-47-006,47,Art. 1941-1945 et 1947 C.c.Q. : avis de modification 3 à 6 mois avant le terme ; refus dans le mois ; demande au TAL dans le mois,SRC-A-001,notes-A §4,2026-10-31,verifie, | |
| 8 | +CLM-47-007,47,Règlement sur les critères de fixation (D. 1455-2025 en vigueur 2026-01-01) : six critères art. 3 ; pourcentage de base IPC moyen art. 3.1 ; 5 % sur immobilisations ; base 2026 = 3,1 % ; 2027 non publié,SRC-A-003;SRC-A-004;SRC-E-048,LégisQuébec + Gazette + page TAL lues (notes-A §6.2),2026-10-31,verifie,2027 à vérifier en janvier 2027 | |
| 9 | +CLM-47-008,47,La valeur locative de marché n'est pas un critère de fixation (résumé TAL Société de financement alouette c. A. 2022),SRC-A-001,résumés de décisions sur le site du TAL (notes-A §6.3) ; pas de référence neutre,2026-10-31,partiellement_verifie,source secondaire officielle (résumé) ; décision non lue | |
| 10 | +CLM-47-009,47,Art. 1896 et 1950 C.c.Q. : avis du loyer le plus bas des 12 mois (section G) ; nouveau locataire 10 jours pour demander la fixation,SRC-A-001,notes-A §4,2026-10-31,verifie, | |
| 11 | +CLM-47-010,47,Loi 31 (2024 c. 2) : art. 1978.2 refus de cession pour motif autre que sérieux → bail résilié ; 1978.3 aucune contrepartie,SRC-A-001;SRC-A-013,notes-A §4,2026-10-31,verifie, | |
| 12 | +CLM-47-011,47,Loi 65 (2024 c. 23) art. 1 : aucune éviction (art. 1959) avant le 6 juin 2027 ; art. 1959.1 protection 65 ans/10 ans/125 % ; art. 1965 indemnité 1 mois par année entre 3 et 24 mois,SRC-A-001;SRC-A-014,notes-A §5,2026-10-31,verifie,aucun avis de fin anticipée repéré au 2026-10-31 | |
| 13 | +CLM-47-012,47,Art. 1904 C.c.Q. : aucun dépôt ; art. 947 pleine propriété,SRC-A-001,notes-A,2026-10-31,verifie, | |
| 14 | +CLM-47-013,47,Formulaire DV de l'OACIQ obligatoire seulement pour les immeubles résidentiels de moins de 5 logements,SRC-A-026;SRC-A-028,notes-A §2.4,2026-10-31,verifie, | |
| 15 | +CLM-47-014,47,Loi sur le TAL art. 51-54.13 : conversion en copropriété divise soumise à autorisation ; interdite dans l'agglomération de Montréal sauf dérogation,SRC-A-002,notes-A §6.1,2026-10-31,verifie, | |
| 16 | +CLM-47-015,47,LAU art. 113 al. 2 par. 18° : pouvoir de régir les usages dérogatoires protégés par droits acquis,SRC-B-0xx (LAU),notes-B ch. 8,2026-10-31,verifie,id registre LAU à compléter par l'orchestrateur | |
| 17 | +CLM-47-016,47,LFM art. 43 (valeur réelle) ; art. 45.1 vendeur réputé détenir les droits du locataire ; art. 46 date de référence = 1er juillet du 2e exercice précédent (rôle 2026-2028 → 2024-07-01),SRC-B-001 (LFM),notes-B §9.2,2026-10-31,verifie, | |
| 18 | +CLM-47-017,47,Rôle 2026-2028 de Montréal : déposé 2025-09-10 ; agglomération +12,2 % ; résidentiel +9,8 % ; 6 logements et plus +10,9 % ; facteur comparatif 1,00,SRC-E-051;SRC-E-052;SRC-B-031;SRC-B-030,communiqués Ville + presse (notes-B §9.9 ; notes-E ch. 47),2026-10-31,verifie,variation Rosemont non trouvée | |
| 19 | +CLM-47-018,47,Taxe foncière générale résidentielle Montréal 2026 : 0,4631 $/100 $ ; hausse moyenne 3,8 % ; Rosemont 5,3 %,SRC-E-031;SRC-B-024;SRC-B-023,Le Devoir 2026-01-12 (document officiel GED non lisible),2026-10-31,partiellement_verifie,presse ; taux « 6 logements et plus » et taxe d'arrondissement NON VÉRIFIÉS | |
| 20 | +CLM-47-019,47,Taux « 6 logements et plus » de Montréal et taxe d'arrondissement Rosemont 2026,—,document « Taux de taxes 2026 » non accessible par script,2026-10-31,non_verifie,normalisation au taux général ; sensibilité ±1 006 $ (ex. 47.2) | |
| 21 | +CLM-47-020,47,Taxe scolaire 2026-2027 : 0,07899 $/100 $ (exemption des premiers 25 000 $),SRC-E-026,communiqué CSS (avis officiel G.O. non ouvert),2026-10-31,verifie,avis G.O. 158 G.O. 1 520 non consulté | |
| 22 | +CLM-47-021,47,Droits de mutation Montréal 2026 : 7 tranches 0,5 % ≤ 62 900 ; 1 % ≤ 315 000 ; 1,5 % ≤ 552 300 ; 2 % ≤ 1 104 700 ; 2,5 % ≤ 2 136 500 ; 3,5 % ≤ 3 113 000 ; 4 % au-delà ; base = max(prix, contrepartie, valeur marchande) (LDMI art. 2),SRC-B-030;SRC-B-034,page Ville 2026-01-12 + LégisQuébec,2026-10-31,verifie, | |
| 23 | +CLM-47-022,47,SCHL oct. 2025 : zone Rosemont loyers 1 ch. 1 055 / 2 ch. 1 348 / 3 ch.+ 1 750 / tous 1 237 ; univers 44 554 ; RMR inoccupation 2,9 % (2,1 %) ; 6-19 log. île 2,2 % et 1 177 $ ; variation 7,4 % / 7,2 % ; rotation 9,4 % (11,1 %),SRC-E-007,tableaux Excel SCHL (notes-E ch. 4 15 27),2026-10-31,verifie, | |
| 24 | +CLM-47-023,47,SCHL Perspectives été 2026 : inoccupation RMR Montréal 3,8 % (2026) 4,5 % (2027) 4,8 % (2028),SRC-C-0xx (schlpmh2026ete),notes-C §4.6,2026-10-31,verifie, | |
| 25 | +CLM-47-024,47,APCIQ T3 2026 : plex Rosemont 950 000 $ (+2 %) ; île 900 000 $ ; RMR 860 500 $,SRC-E-001;SRC-E-004,communiqué et baromètre (notes-E),2026-10-31,verifie,attribution APCIQ/Centris requise | |
| 26 | +CLM-47-025,47,CBRE T3 2026 Montréal low-rise B 4,25-4,75 % (en hausse) ; écart avec obligation 10 ans,SRC-E-045,rapport CBRE publié 2026-10-21,2026-10-31,verifie,industrie ; valeurs ponctuelles seulement | |
| 27 | +CLM-47-026,47,Altus : 2,9 G$ investis en multirésidentiel à Montréal au S1 2026 (+30 %),SRC-E-0xx (altusmontreal2026q2),notes-E ch. 29,2026-10-31,verifie, | |
| 28 | +CLM-47-027,47,Banque du Canada : taux directeur 2,25 % du 2025-10-30 au 2026-10-28 puis 2,50 % ; obligations 5 ans 3,58 % et 10 ans 3,84 % (2026-10-29) ; hypothécaire 5 ans affiché 6,19 %,SRC-C-027;SRC-C-028;SRC-C-029;SRC-C-030,communiqué + Valet (notes-C §4.1),2026-10-31,verifie, | |
| 29 | +CLM-47-028,47,Fonds avancés prêts assurés fixe ≥ 5 ans : 4,12 % (août 2026),SRC-E-012,Valet A4_RATES_MORTGAGES,2026-10-31,verifie, | |
| 30 | +CLM-47-029,47,Loi sur l'intérêt L.R.C. 1985 c. I-15 art. 6 : taux exprimé en composition semestrielle ou annuelle,—,docstring finance.py et annexe conventions ; texte fédéral non consulté dans les lots A-E,2026-10-31,partiellement_verifie,à vérifier sur laws-lois.justice.gc.ca | |
| 31 | +CLM-47-030,47,BSIF : taux admissible minimal non assuré = max(taux + 2 %, 5,25 %),SRC-C-035 (osfiMqr),page BSIF modifiée 2026-01-29 (notes-C §4.2),2026-10-31,verifie, | |
| 32 | +CLM-47-031,47,APH Select : 50 points → RPV 85 % existant / amortissement 40 ans / RCD 1,10 ; 70 et 100 points → 95 % et 45/50 ans ; critère abordabilité 40 % des logements ≤ 30 % du revenu médian des locataires ; engagement 10 ans,SRC-C-040,page SCHL 2026-09-21 (notes-C §4.4),2026-10-31,partiellement_verifie,cellules 70/100 points déduites de la mise en page ; admissibilité du sujet non examinée | |
| 33 | +CLM-47-032,47,Primes SCHL immeubles collectifs : 2,60 % ≤ 65 % ; 2,85 ; 3,35 ; 4,35 % ≤ 80 % ; 5,35 % ≤ 85 % ; surprime 0,25 % par 5 ans au-delà de 25 ans,SRC-C-041,PDF SCHL (notes-C §4.4),2026-10-31,verifie,PDF non daté | |
| 34 | +CLM-47-033,47,SCHL Frais d'exploitation Québec (avril 2015) : gestion 3 à 5 % du revenu brut selon la taille ; entretien 500 $/log. ; TAL forfait gestion 5 %,SRC-E-047;SRC-E-048,document hébergé par un tiers ; formulaire RN,2026-10-31,partiellement_verifie,document daté (2015) à titre indicatif | |
| 35 | +CLM-47-034,47,IPC Québec +3,1 % (sept. 2026) ; tarif électricité domestique +3,0 % (2026),SRC-E-0xx (statcanipc ; Régie D-2026-033),notes-E ch. 27-28,2026-10-31,verifie, | |
| 36 | +CLM-47-035,47,Teranet–BN Montréal +1,4 % a/a (sept. 2026),SRC-C-0xx (teranet202609),notes-C §4.9,2026-10-31,verifie, | |
| 37 | +CLM-47-036,47,Réserve de remplacement 650-675 $/logement et gestion imputée 4 % : conventions de l'ouvrage (aucune enquête officielle récente),—,data/synthetic/README.md ; notes-E ch. 28 (NON VÉRIFIÉ),2026-10-31,non_verifie,incohérence : docstring cas3.py « 650 $/logement » vs donnée 8 103 $ = 675,25 $/logement (à harmoniser) | |
| 38 | +CLM-47-037,47,Vacance économique 3,9 % ; rotation 15 % ; croissance loyers marché 3,5 % ; r 7,25 % ; R_T 5,0 % ; coûts de vente 2,5 % ; ajustement état 3 %/point : hypothèses de l'auteur,—,déclarées comme hypothèses dans le texte et testées par Monte-Carlo,2026-10-31,non_verifie,hypothèses pédagogiques (données synthétiques) | |
added
verification/chapters/ch47-figures.csv
+8 −0
@@ -0,0 +1,8 @@ | ||
| 1 | +fichier,chapitre,script,donnees,type_donnees,legende_courte,statut | |
| 2 | +ch47/ch47-fig-etat-locatif.pdf,47,analysis/python/figures/ch47_dossier_figures.py,data/synthetic/cas3_montreal_etat_locatif.csv,synthetique,Loyers contractuels vs loyers de marché des 12 logements [données synthétiques],verifie | |
| 3 | +ch47/ch47-fig-depenses.pdf,47,analysis/python/figures/ch47_dossier_figures.py,data/synthetic/cas3_montreal_depenses.csv;cas3_montreal_hypotheses_dcf.json,synthetique,Structure des dépenses normalisées de première année par poste et nature [données synthétiques],verifie | |
| 4 | +ch47/ch47-fig-taux-extraits.pdf,47,analysis/python/figures/ch47_dossier_figures.py,data/synthetic/cas3_montreal_comparables.csv,synthetique,Taux globaux extraits de 40 ventes selon la taille et l'état avec médiane et sujet [données synthétiques],verifie | |
| 5 | +ch47/ch47-fig-flux-dcf.pdf,47,analysis/python/figures/ch47_dossier_figures.py,data/synthetic/cas3_montreal_*.csv;hypotheses_dcf.json,synthetique,RNE CAPEX et flux avant dette sur 10 ans avec service de la dette [données synthétiques],verifie | |
| 6 | +ch47/ch47-fig-montecarlo.pdf,47,analysis/python/figures/ch47_dossier_figures.py,simulation 10 000 tirages graine 20261031 (ch47_dossier.monte_carlo),synthetique,Histogramme de la valeur simulée avec quantiles 5/50/95 DCF et capitalisation [données synthétiques],verifie | |
| 7 | +ch47/ch47-fig-tornade.pdf,47,analysis/python/figures/ch47_dossier_figures.py,ch47_dossier.tornade (quantiles 10/90 des lois déclarées),synthetique,Tornade de la valeur DCF selon sept intrants [données synthétiques],verifie | |
| 8 | +ch47/ch47-fig-rcd-scenarios.pdf,47,analysis/python/figures/ch47_dossier_figures.py,ch47_dossier.scenarios_renouvellement,synthetique,RCD par année sous quatre scénarios de taux au renouvellement [données synthétiques],verifie | |
added
verification/chapters/ch47-registry.csv
+2 −0
@@ -0,0 +1,2 @@ | ||
| 1 | +id,type,titre,organisme,version_ou_date,url,date_consultation,statut,utilise_dans,notes | |
| 2 | +# Aucune nouvelle source : le chapitre 47 ne cite que des clés déjà présentes dans references/library.bib (voir ch47.md). | |
added
verification/chapters/ch47.md
+33 −0
@@ -0,0 +1,33 @@ | ||
| 1 | +# Chapitre 47 — Cas 3 : immeuble de 12 logements à Montréal — bilan de rédaction (2026-10-31) | |
| 2 | + | |
| 3 | +## Livrables | |
| 4 | +- `manuscript/chapters/ch47.tex` : ≈ 12 800 mots de prose (18 300 mots au compteur brut de `check_manuscript.py`, tableaux et équations compris), 14 sections (47.1 mandat → 47.14 rapport), **19 tableaux**, **7 figures**, **18 équations numérotées**, encadrés `synthetique`, `quebec`, `erreurs`, `limites`, `synthese`, **7 exercices** (niveaux 1-1-2-2-2-3-3), « Pour aller plus loin ». 57 clés bibliographiques citées, toutes déjà présentes dans `references/library.bib` (aucune nouvelle entrée : `references/lib-ch47.bib` ne contient qu'un commentaire ; `ch47-registry.csv` vide). | |
| 5 | +- `analysis/python/cases/ch47_dossier.py` : tous les chiffres cités (revenus, dépenses, RNE, taux extraits, capitalisation, DCF, comparaison par unité avec OLS auxiliaire + `traite.comparables`, financement, scénarios de renouvellement, APH Select, Monte-Carlo vectorisé 10 000 tirages graine 20261031, tornade, réconciliation, droits de mutation Montréal 2026, valeur libre de baux, exercices). Appelle `traite.cas3` sans le modifier. Exécution ≈ 0,7 s. | |
| 6 | +- `analysis/python/figures/ch47_dossier_figures.py` → `assets/figures/ch47/ch47-fig-{etat-locatif,depenses,taux-extraits,flux-dcf,montecarlo,tornade,rcd-scenarios}.pdf` (style `traite.style`, 135 mm). | |
| 7 | +- `analysis/python/tests/test_ch47.py` : 8 tests (identités, valeurs citées ±1 $, reproduction exacte de `cas3.projection_dcf` par la version vectorisée, reproductibilité MC même graine / robustesse autre graine, exercices) — **vert** (`8 passed`), `test_cas3.py` toujours vert. | |
| 8 | +- `solutions/ch47.tex` : 7 solutions détaillées (chiffres de `exercices()`). | |
| 9 | +- `verification/chapters/ch47-figures.csv` (7 lignes), `ch47-calculations.csv` (26 lignes), `ch47-claims.csv` (37 lignes), `ch47-registry.csv` (aucune ligne), `references/lib-ch47.bib` (aucune entrée) — à fusionner par l'orchestrateur. | |
| 10 | + | |
| 11 | +## Contrôles | |
| 12 | +- `tools/compile_chapter.sh 47` → `OK ch47 : 50 pages, 0 references/citations non definies, 0 overfull hbox` (dernière passe ; voir sortie console). Inspection visuelle : `pdftoppm -r 50` sur les 50 pages, pages 6, 9, 10, 16, 22, 28, 34, 40 vérifiées (tableaux dans la justification, figures à la largeur du texte, encadrés, exercices). | |
| 13 | +- `python3 -m pytest -q analysis/python/tests/test_ch47.py` → `8 passed`. | |
| 14 | +- `tools/check_manuscript.py --no-log` : 7 « problèmes » = les 7 figures du ch. 47 absentes de `verification/figures.csv` global (lignes fournies dans `ch47-figures.csv`, fusion non faite par le rédacteur conformément à la consigne). | |
| 15 | + | |
| 16 | +## Chiffres pivots (coïncident avec `traite.cas3.tout()` donc avec ch. 29-31) | |
| 17 | +RBP 165 540 ; RBE 168 443,94 ; dépenses 78 488,19 (dont réserve 8 103) ; RNE 89 955,75 ; taux médian 4,415 % (n = 40) ; V_cap 2 037 502,89 ; V_DCF 2 208 565,91 (VT nette 3 113 338,33 ; part terminale 70,0 %) ; comparaison par unité 1 870 902,67 (MRB 11,94 → 1 976 548) ; prêt RCD 1,20 = 1 475 959,45 (liant ; RPV 75 % = 1 656 424 sur V_DCF) ; SD 74 963,13 ; TRI FP 11,22 % ; point mort 92,7 % ; MC : médiane 2 191 608, IC 90 % [1 735 194 ; 2 876 902], P(RCD₁<1,20) 49,4 %, P(RCD<1,0) = 0 ; **valeur finale 2 060 000 $** (45/35/20), 171 667 $/log., taux implicite 4,37 %, 93,6 % du rôle ; droits de mutation 41 325,50 $. | |
| 18 | + | |
| 19 | +## Points non vérifiés / à harmoniser | |
| 20 | +1. Taux de taxe foncière Montréal 2026 « 6 logements et plus » et taxe d'arrondissement Rosemont : non vérifiés (document officiel non lisible) ; normalisation au taux général 0,4631 $/100 $ (source presse) ; sensibilité −1 006 $ de RNE (ex. 47.2). Claims CLM-47-018/019. | |
| 21 | +2. **Réserve de remplacement** : la docstring de `traite/cas3.py` annonce « 650 $/logement (convention du livre) » mais le code reprend l'historique 2025 = 8 103 $ = 675,25 $/logement. Le chapitre écrit « 8 103 $ (675 $/logement) » et signale la convention ≈ 650 $. À harmoniser (donnée ou docstring) avec les ch. 28-30. | |
| 22 | +3. Pourcentage TAL 2027 non publié ; croissance contractuelle = pourcentage de base 3,1 % seulement. | |
| 23 | +4. Rotation 15 % > taux SCHL régional 9,4 % (hypothèse déclarée, testée 8-25 %). | |
| 24 | +5. Loi sur l'intérêt (fédérale, art. 6) citée en note de bas de page sans clé bib (aucune entrée dans library.bib) — CLM-47-029 `partiellement_verifie`. | |
| 25 | +6. APH Select : cellules 70/100 points et admissibilité du sujet non vérifiées (CLM-47-031). | |
| 26 | +7. Résumé TAL « valeur locative non critère » : source secondaire officielle, décision non lue (CLM-47-008). | |
| 27 | +8. `ch47-claims.csv` : quelques `source_id` indiqués « SRC-B-0xx » / « SRC-C-0xx » (LAU, LFM, schlpmh2026ete, altus, statcanipc, teranet) à compléter par l'orchestrateur avec les identifiants exacts du registre fusionné. | |
| 28 | +9. Renvois inter-chapitres : macro locale `\chref{NN}` (= `\cref{ch:NN}` si le label existe, « chapitre NN » sinon) pour permettre la compilation isolée à 0 référence non définie ; la convention « \cref sans article » du 2026-10-31 est respectée (article écrit dans le texte). L'orchestrateur peut remplacer `\chref` par `\cref` lors de l'harmonisation. | |
| 29 | + | |
| 30 | +## Suggestions | |
| 31 | +- Vérifier que les ch. 29/30/31 présentent le financement sur la même base (V_DCF) ; le ch. 47 le recalcule aussi à la valeur finale (mise de fonds 584 041 $, RPV 71,6 %). | |
| 32 | +- Le fichier `data/synthetic/cas3_montreal_sujet.json` ne contient ni lot ni description des composantes : la fiche d'inspection (tab. 47.2) est narrative ; si les ch. 11/28 veulent la réutiliser, l'ajouter au JSON. | |
| 33 | +- Reproduire la figure 47.5 (Monte-Carlo) et le tab. 47.16 au ch. 40 si celui-ci utilise le cas 3 : même graine, mêmes lois (`MC_LOIS`). | |
modified
verification/claims.csv
+37 −0
@@ -66,3 +66,40 @@ CLM-30-14,30,"Loi sur l'intérêt (L.R.C. 1985, c. I-15, art. 6) : taux hypothé | ||
| 66 | 66 | CLM-30-15,30,Droits sur les mutations immobilières payés par l'acquéreur (Québec),SRC-A-021,"clé bib quebecMutations ; Loi concernant les droits sur les mutations immobilières (D-15.1), art. sur le débiteur du droit non relu dans ce lot",2026-10-31,partiellement_verifie,vérifier l'article qui désigne l'acquéreur comme débiteur |
| 67 | 67 | CLM-30-16,30,"SCHL APH Select : RCD minimal 1,10 pour le locatif ordinaire",SRC-C-040,Page APH Select publiée 2026-09-21 lue le 2026-10-31 (notes-C.md §4.4),2026-10-31,partiellement_verifie,cellules du tableau SCHL partiellement déduites (notes-C.md point 7) |
| 68 | 68 | CLM-30-17,30,"Aucune enquête publique vérifiée ne publie de taux d'actualisation pour le multirésidentiel québécois ; le taux de 7,25 % du cas 3 est une hypothèse",,constat d'absence (notes-C,notes-E),2026-10-31,non_verifie |
| 69 | +CLM-47-001,47,Définition OEAQ de la valeur marchande (« prix sincère le plus probable… marché libre et ouvert à la concurrence ») et quatre conditions,SRC-C-001,NPP p. Intro-11 lues (notes-C §1.1),2026-10-31,verifie,citation ≤ 25 mots ; conditions paraphrasées | |
| 70 | +CLM-47-002,47,Contenu minimal du rapport écrit : 12 éléments (règle 2.3) ; inspection obligatoire (règle 2.4) ; hypothèses extraordinaires rappelées à chaque conclusion (2.1 c et 2.3 él. 7) ; rejet d'une méthode justifié,SRC-C-001,NPP lues (notes-C §2.1),2026-10-31,verifie, | |
| 71 | +CLM-47-003,47,Code de déontologie : n'agir que pour une partie (art. 20) ; divulgation double rôle (18-19) ; honoraires conditionnels interdits (24.1) ; signature et qualité (34),SRC-C-003,LégisQuébec C-26 r. 123 à jour au 2026-07-01 (notes-C §2.2),2026-10-31,verifie, | |
| 72 | +CLM-47-004,47,IVS 102 annexe A10 : définition de la market value (édition en vigueur 31 janv. 2025),SRC-C-014,PDF officiel (notes-C §1.2),2026-10-31,verifie, | |
| 73 | +CLM-47-005,47,Art. 1937 C.c.Q. : l'aliénation ne permet pas au nouveau locateur de résilier le bail ; 1936 maintien dans les lieux,SRC-A-001,LégisQuébec à jour au 2026-10-15 (notes-A §4),2026-10-31,verifie, | |
| 74 | +CLM-47-006,47,Art. 1941-1945 et 1947 C.c.Q. : avis de modification 3 à 6 mois avant le terme ; refus dans le mois ; demande au TAL dans le mois,SRC-A-001,notes-A §4,2026-10-31,verifie, | |
| 75 | +CLM-47-007,47,Règlement sur les critères de fixation (D. 1455-2025 en vigueur 2026-01-01) : six critères art. 3 ; pourcentage de base IPC moyen art. 3.1 ; 5 % sur immobilisations ; base 2026 = 3,1 % ; 2027 non publié,SRC-A-003;SRC-A-004;SRC-E-048,LégisQuébec + Gazette + page TAL lues (notes-A §6.2),2026-10-31,verifie | |
| 76 | +CLM-47-008,47,La valeur locative de marché n'est pas un critère de fixation (résumé TAL Société de financement alouette c. A. 2022),SRC-A-001,résumés de décisions sur le site du TAL (notes-A §6.3) ; pas de référence neutre,2026-10-31,partiellement_verifie,source secondaire officielle (résumé) ; décision non lue | |
| 77 | +CLM-47-009,47,Art. 1896 et 1950 C.c.Q. : avis du loyer le plus bas des 12 mois (section G) ; nouveau locataire 10 jours pour demander la fixation,SRC-A-001,notes-A §4,2026-10-31,verifie, | |
| 78 | +CLM-47-010,47,Loi 31 (2024 c. 2) : art. 1978.2 refus de cession pour motif autre que sérieux → bail résilié ; 1978.3 aucune contrepartie,SRC-A-001;SRC-A-013,notes-A §4,2026-10-31,verifie, | |
| 79 | +CLM-47-011,47,Loi 65 (2024 c. 23) art. 1 : aucune éviction (art. 1959) avant le 6 juin 2027 ; art. 1959.1 protection 65 ans/10 ans/125 % ; art. 1965 indemnité 1 mois par année entre 3 et 24 mois,SRC-A-001;SRC-A-014,notes-A §5,2026-10-31,verifie,aucun avis de fin anticipée repéré au 2026-10-31 | |
| 80 | +CLM-47-012,47,Art. 1904 C.c.Q. : aucun dépôt ; art. 947 pleine propriété,SRC-A-001,notes-A,2026-10-31,verifie, | |
| 81 | +CLM-47-013,47,Formulaire DV de l'OACIQ obligatoire seulement pour les immeubles résidentiels de moins de 5 logements,SRC-A-026;SRC-A-028,notes-A §2.4,2026-10-31,verifie, | |
| 82 | +CLM-47-014,47,Loi sur le TAL art. 51-54.13 : conversion en copropriété divise soumise à autorisation ; interdite dans l'agglomération de Montréal sauf dérogation,SRC-A-002,notes-A §6.1,2026-10-31,verifie, | |
| 83 | +CLM-47-015,47,LAU art. 113 al. 2 par. 18° : pouvoir de régir les usages dérogatoires protégés par droits acquis,SRC-B-0xx (LAU),notes-B ch. 8,2026-10-31,verifie,id registre LAU à compléter par l'orchestrateur | |
| 84 | +CLM-47-016,47,LFM art. 43 (valeur réelle) ; art. 45.1 vendeur réputé détenir les droits du locataire ; art. 46 date de référence = 1er juillet du 2e exercice précédent (rôle 2026-2028 → 2024-07-01),SRC-B-001 (LFM),notes-B §9.2,2026-10-31,verifie, | |
| 85 | +CLM-47-017,47,Rôle 2026-2028 de Montréal : déposé 2025-09-10 ; agglomération +12,2 % ; résidentiel +9,8 % ; 6 logements et plus +10,9 % ; facteur comparatif 1,00,SRC-E-051;SRC-E-052;SRC-B-031;SRC-B-030 | |
| 86 | +CLM-47-018,47,Taxe foncière générale résidentielle Montréal 2026 : 0,4631 $/100 $ ; hausse moyenne 3,8 % ; Rosemont 5,3 %,SRC-E-031;SRC-B-024;SRC-B-023,Le Devoir 2026-01-12 (document officiel GED non lisible) | |
| 87 | +CLM-47-019,47,Taux « 6 logements et plus » de Montréal et taxe d'arrondissement Rosemont 2026,—,document « Taux de taxes 2026 » non accessible par script,2026-10-31,non_verifie,normalisation au taux général ; sensibilité ±1 006 $ (ex. 47.2) | |
| 88 | +CLM-47-020,47,Taxe scolaire 2026-2027 : 0,07899 $/100 $ (exemption des premiers 25 000 $),SRC-E-026,communiqué CSS (avis officiel G.O. non ouvert),2026-10-31,verifie | |
| 89 | +CLM-47-021,47,Droits de mutation Montréal 2026 : 7 tranches 0,5 % ≤ 62 900 ; 1 % ≤ 315 000 ; 1,5 % ≤ 552 300 ; 2 % ≤ 1 104 700 ; 2,5 % ≤ 2 136 500 ; 3,5 % ≤ 3 113 000 ; 4 % au-delà ; base = max(prix,contrepartie | |
| 90 | +CLM-47-022,47,SCHL oct. 2025 : zone Rosemont loyers 1 ch. 1 055 / 2 ch. 1 348 / 3 ch.+ 1 750 / tous 1 237 ; univers 44 554 ; RMR inoccupation 2,9 % (2,1 %) ; 6-19 log. île 2,2 % et 1 177 $ ; variation 7,4 % / 7,2 % ; rotation 9 | |
| 91 | +CLM-47-023,47,SCHL Perspectives été 2026 : inoccupation RMR Montréal 3,8 % (2026) 4,5 % (2027) 4,8 % (2028),SRC-C-0xx (schlpmh2026ete),notes-C §4.6 | |
| 92 | +CLM-47-024,47,APCIQ T3 2026 : plex Rosemont 950 000 $ (+2 %) ; île 900 000 $ ; RMR 860 500 $,SRC-E-001;SRC-E-004,communiqué et baromètre (notes-E),2026-10-31,verifie,attribution APCIQ/Centris requise | |
| 93 | +CLM-47-025,47,CBRE T3 2026 Montréal low-rise B 4,25-4,75 % (en hausse) ; écart avec obligation 10 ans,SRC-E-045,rapport CBRE publié 2026-10-21,2026-10-31 | |
| 94 | +CLM-47-026,47,Altus : 2,9 G$ investis en multirésidentiel à Montréal au S1 2026 (+30 %),SRC-E-0xx (altusmontreal2026q2),notes-E ch. 29,2026-10-31,verifie | |
| 95 | +CLM-47-027,47,Banque du Canada : taux directeur 2,25 % du 2025-10-30 au 2026-10-28 puis 2,50 % ; obligations 5 ans 3,58 % et 10 ans 3,84 % (2026-10-29) ; hypothécaire 5 ans affiché 6,19 % | |
| 96 | +CLM-47-028,47,Fonds avancés prêts assurés fixe ≥ 5 ans : 4,12 % (août 2026),SRC-E-012,Valet A4_RATES_MORTGAGES,2026-10-31,verifie | |
| 97 | +CLM-47-029,47,Loi sur l'intérêt L.R.C. 1985 c. I-15 art. 6 : taux exprimé en composition semestrielle ou annuelle,—,docstring finance.py et annexe conventions ; texte fédéral non consulté dans les lots A-E,2026-10-31,partiellement_verifie,à vérifier sur laws-lois.justice.gc.ca | |
| 98 | +CLM-47-030,47,BSIF : taux admissible minimal non assuré = max(taux + 2 %,5,25 %),SRC-C-035 (osfiMqr),page BSIF modifiée 2026-01-29 (notes-C §4.2),2026-10-31 | |
| 99 | +CLM-47-031,47,APH Select : 50 points → RPV 85 % existant / amortissement 40 ans / RCD 1,10 ; 70 et 100 points → 95 % et 45/50 ans ; critère abordabilité 40 % des logements ≤ 30 % du revenu médian des locataires ; engagement 10 ans,SRC-C-040,page SCHL 2026-09-21 (notes-C §4.4),2026-10-31,partiellement_verifie | |
| 100 | +CLM-47-032,47,Primes SCHL immeubles collectifs : 2,60 % ≤ 65 % ; 2,85 ; 3,35 ; 4,35 % ≤ 80 % ; 5,35 % ≤ 85 % ; surprime 0 | |
| 101 | +CLM-47-033,47,SCHL Frais d'exploitation Québec (avril 2015) : gestion 3 à 5 % du revenu brut selon la taille ; entretien 500 $/log. ; TAL forfait gestion 5 %,SRC-E-047;SRC-E-048,document hébergé par un tiers ; formulaire RN,2026-10-31,partiellement_verifie,document daté (2015) à titre indicatif | |
| 102 | +CLM-47-034,47,IPC Québec +3,1 % (sept. 2026) ; tarif électricité domestique +3,0 % (2026),SRC-E-0xx (statcanipc ; Régie D-2026-033),notes-E ch. 27-28,2026-10-31 | |
| 103 | +CLM-47-035,47,Teranet–BN Montréal +1,4 % a/a (sept. 2026),SRC-C-0xx (teranet202609),notes-C §4.9,2026-10-31,verifie | |
| 104 | +CLM-47-036,47,Réserve de remplacement 650-675 $/logement et gestion imputée 4 % : conventions de l'ouvrage (aucune enquête officielle récente),—,data/synthetic/README.md ; notes-E ch. 28 (NON VÉRIFIÉ),2026-10-31,non_verifie,incohérence : docstring cas3.py « 650 $/logement » vs donnée 8 103 $ = 675 | |
| 105 | +CLM-47-037,47,Vacance économique 3,9 % ; rotation 15 % ; croissance loyers marché 3,5 % ; r 7,25 % ; R_T 5,0 % ; coûts de vente 2,5 % ; ajustement état 3 %/point : hypothèses de l'auteur | |
modified
verification/figures.csv
+7 −0
@@ -18,3 +18,10 @@ ch30/ch30-fig-loyers.pdf,30,analysis/python/figures/ch30_loyers.py,data/syntheti | ||
| 18 | 18 | ch30/ch30-fig-flux.pdf,30,analysis/python/figures/ch30_flux.py,data/synthetic/cas3_montreal_*.csv;cas3_montreal_hypotheses_dcf.json,synthetique,"RNE, CAPEX, flux avant dette (barres) et VA cumulée + VA de la valeur terminale, cas 3 [données synthétiques]",verifie |
| 19 | 19 | ch30/ch30-fig-surface.pdf,30,analysis/python/figures/ch30_surface.py,data/synthetic/cas3_montreal_*.csv;cas3_montreal_hypotheses_dcf.json,synthetique,"Heatmap V(r, R_T) du DCF du cas 3, 9 x 7 cellules, base encadrée [données synthétiques]",verifie |
| 20 | 20 | ch30/ch30-fig-tornade.pdf,30,analysis/python/figures/ch30_tornade.py,data/synthetic/cas3_montreal_*.csv;cas3_montreal_hypotheses_dcf.json,synthetique,"Diagramme en tornade de la valeur DCF du cas 3, 9 intrants [données synthétiques]",verifie |
| 21 | +ch47/ch47-fig-etat-locatif.pdf,47,analysis/python/figures/ch47_dossier_figures.py,data/synthetic/cas3_montreal_etat_locatif.csv,synthetique,Loyers contractuels vs loyers de marché des 12 logements [données synthétiques],verifie | |
| 22 | +ch47/ch47-fig-depenses.pdf,47,analysis/python/figures/ch47_dossier_figures.py,data/synthetic/cas3_montreal_depenses.csv;cas3_montreal_hypotheses_dcf.json,synthetique,Structure des dépenses normalisées de première année par poste et nature [données synthétiques],verifie | |
| 23 | +ch47/ch47-fig-taux-extraits.pdf,47,analysis/python/figures/ch47_dossier_figures.py,data/synthetic/cas3_montreal_comparables.csv,synthetique,Taux globaux extraits de 40 ventes selon la taille et l'état avec médiane et sujet [données synthétiques],verifie | |
| 24 | +ch47/ch47-fig-flux-dcf.pdf,47,analysis/python/figures/ch47_dossier_figures.py,data/synthetic/cas3_montreal_*.csv;hypotheses_dcf.json,synthetique,RNE CAPEX et flux avant dette sur 10 ans avec service de la dette [données synthétiques],verifie | |
| 25 | +ch47/ch47-fig-montecarlo.pdf,47,analysis/python/figures/ch47_dossier_figures.py,simulation 10 000 tirages graine 20261031 (ch47_dossier.monte_carlo),synthetique,Histogramme de la valeur simulée avec quantiles 5/50/95 DCF et capitalisation [données synthétiques],verifie | |
| 26 | +ch47/ch47-fig-tornade.pdf,47,analysis/python/figures/ch47_dossier_figures.py,ch47_dossier.tornade (quantiles 10/90 des lois déclarées),synthetique,Tornade de la valeur DCF selon sept intrants [données synthétiques],verifie | |
| 27 | +ch47/ch47-fig-rcd-scenarios.pdf,47,analysis/python/figures/ch47_dossier_figures.py,ch47_dossier.scenarios_renouvellement,synthetique,RCD par année sous quatre scénarios de taux au renouvellement [données synthétiques],verifie | |
| 21 | 28 | |