SPB Git
22.0 KB · 329 lines python
1#!/usr/bin/env python32"""Chapitre 5 — Économie urbaine et rente foncière : calculs de l'exemple intégral et des encadrés.34Imprime tous les chiffres cités dans le chapitre 5 :5  * modèle monocentrique (Alonso–Muth–Mills) simulé avec des PARAMÈTRES PÉDAGOGIQUES DÉCLARÉS (section PARAMS) :6    gradient du prix du logement p(d) = p0 (1 - t d / y)^(1/beta), gradient exponentiel approché, rente foncière7    r(d) = r0 (p(d)/p0)^(1/alpha), densité, frontière urbaine, courbes d'enchère de von Thünen et d'Alonso ;8  * gradient prix/distance observé dans le dossier du cas 1 (Gatineau, DONNÉES SYNTHÉTIQUES, graine 20261031) :9    tranches de distance, régressions avec et sans indicatrices de secteur (traite.hedonique.mco), coefficient10    `dist_centre` du modèle hédonique de référence (traite.cas1.hedonique) ;11  * décomposition interrégionale de la valeur par logement du cas 4 (traite.cas4.sommaire) ;12  * valeur résiduelle du terrain et effet de levier ;13  * capitalisation de la taxe foncière : formule exacte et approchée, taux effectif, taux 2026 de Québec,14    Gatineau et Montréal (sources : notes-B/E du dépôt), grille de sensibilité.1516Usage (racine du dépôt) : PYTHONPATH=analysis/python python3 analysis/python/cases/ch05_rente.py17Les régressions utilisent traite.hedonique.mco ; les données des cas passent par traite.cas1 et traite.cas4."""18from __future__ import annotations19import sys, pathlib, math20import numpy as np, pandas as pd21sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1]))22from traite import cas1, cas423from traite import hedonique as h2425ROOT = pathlib.Path(__file__).resolve().parents[3]2627# ----------------------------------------------------------------------------- paramètres pédagogiques (déclarés)28PARAMS = {29    "y": 80000.0,     # revenu annuel du ménage ($/an)30    "t": 700.0,       # coût annuel de navettage par kilomètre de distance au centre ($/km/an), temps compris31    "beta": 0.30,     # part du revenu net de transport consacrée au logement32    "alpha": 0.30,    # part du terrain dans le coût de production du logement (Cobb-Douglas)33    "p0": 260.0,      # prix annuel du service de logement au centre ($/m² de plancher/an)34    "r0": 80.0,       # rente foncière annuelle au centre ($/m² de terrain/an)35    "ra": 2.0,        # rente alternative (agricole, conversion comprise) à la frontière ($/m²/an)36    "i": 0.05,        # taux de capitalisation des rentes foncières (annuel)37}38# courbes d'enchère linéaires de trois usages (illustration d'Alonso) : (rente au centre $/m²/an, pente $/m²/an/km)39ALONSO = {"Commerces et bureaux": (300.0, 25.0), "Résidentiel dense": (120.0, 6.0), "Résidentiel pavillonnaire": (60.0, 1.8)}40# von Thünen (exercice 5.1) : rendement Y (t/ha), prix p ($/t), coût c ($/t), fret f ($/t/km)41THUNEN = {"Maraîchage": dict(Y=20.0, p=300.0, c=250.0, f=3.0), "Grandes cultures": dict(Y=4.0, p=500.0, c=350.0, f=5.0)}4243# taux de taxe foncière 2026 (par 100 $ d'évaluation) — sources : vqtaux2026 ; gatineaureglement994 ; ledevoirmtl2026 (secondaire) ; taxescolaire202644TAUX_2026 = {45    "Québec (1 à 5 logements)": 0.7464, "Québec (6 logements et plus)": 0.7697,46    "Gatineau (taux de base)": 0.5224, "Montréal (taxe foncière générale, hors arrondissement)": 0.4631,47    "Taxe scolaire 2026-2027 (toute la province)": 0.07899,48}49FACTEUR_2026 = {"Gatineau": 1.02, "Montréal": 1.00}   # facteurs comparatifs 2026 publiés (gatmutations ; mtlmutations2026)505152# ----------------------------------------------------------------------------- von Thünen et Alonso53def rente_thunen(d, Y: float, p: float, c: float, f: float) -> np.ndarray:54    """R(d) = Y (p - c) - Y f d  (rente par hectare et par an)."""55    return Y * (p - c) - Y * f * np.asarray(d, float)5657def distance_limite_thunen(Y: float, p: float, c: float, f: float) -> float:58    """Distance où la rente s'annule : d* = (p - c) / f."""59    return (p - c) / f6061def frontiere_usages(R1: float, t1: float, R2: float, t2: float) -> float:62    """Distance où deux courbes d'enchère linéaires R1 - t1 d et R2 - t2 d se croisent."""63    return (R1 - R2) / (t1 - t2)6465def enveloppe_alonso(d, usages: dict = ALONSO, ra: float = PARAMS["ra"]) -> tuple[np.ndarray, list]:66    d = np.asarray(d, float)67    courbes = {k: R0 - t * d for k, (R0, t) in usages.items()}68    env = np.maximum.reduce([*courbes.values(), np.full_like(d, ra)])69    return env, courbes707172# ----------------------------------------------------------------------------- modèle monocentrique73def prix_logement(d, P: dict = PARAMS) -> np.ndarray:74    """p(d) = p0 (1 - t d / y)^(1/beta) : solution exacte de la condition de Muth avec part budgétaire constante."""75    d = np.asarray(d, float)76    base = np.clip(1.0 - P["t"] * d / P["y"], 1e-12, None)77    return P["p0"] * base ** (1.0 / P["beta"])7879def gradient_exponentiel(P: dict = PARAMS) -> float:80    """Pente logarithmique du prix au centre : gamma = t / (beta y) (approximation exponentielle)."""81    return P["t"] / (P["beta"] * P["y"])8283def prix_exponentiel(d, P: dict = PARAMS) -> np.ndarray:84    return P["p0"] * np.exp(-gradient_exponentiel(P) * np.asarray(d, float))8586def consommation_logement(d, P: dict = PARAMS) -> np.ndarray:87    """q(d) = beta (y - t d) / p(d) : surface de plancher consommée par ménage (m²)."""88    d = np.asarray(d, float)89    return P["beta"] * (P["y"] - P["t"] * d) / prix_logement(d, P)9091def rente_fonciere(d, P: dict = PARAMS) -> np.ndarray:92    """r(d) = r0 (p(d)/p0)^(1/alpha) : profit nul des producteurs de logement (Cobb-Douglas)."""93    return P["r0"] * (prix_logement(d, P) / P["p0"]) ** (1.0 / P["alpha"])9495def intensite_capital(d, P: dict = PARAMS) -> np.ndarray:96    """Plancher par m² de terrain, normalisé à 1 au centre : h(d)/h(0) = (p(d)/p0)^((1-alpha)/alpha)."""97    return (prix_logement(d, P) / P["p0"]) ** ((1.0 - P["alpha"]) / P["alpha"])9899def densite(d, P: dict = PARAMS) -> np.ndarray:100    """Ménages par m² de terrain, normalisée à 1 au centre : D(d) = h(d)/q(d)."""101    d = np.asarray(d, float)102    D = intensite_capital(d, P) / consommation_logement(d, P)103    return D / (intensite_capital(0.0, P) / consommation_logement(0.0, P))104105def frontiere_urbaine(P: dict = PARAMS) -> float:106    """Distance d_bar où r(d_bar) = ra (solution analytique)."""107    ratio_p = (P["ra"] / P["r0"]) ** P["alpha"]            # p(d_bar)/p0108    return (1.0 - ratio_p ** P["beta"]) * P["y"] / P["t"]109110def demi_distance(gamma: float) -> float:111    """Distance qui divise le prix par deux sous un gradient exponentiel : ln 2 / gamma."""112    return math.log(2.0) / gamma113114def valeur_terrain(rente: float, i: float = PARAMS["i"], g: float = 0.0) -> float:115    """Valeur du terrain = rente / (i - g) (rente perpétuelle, croissance g)."""116    return rente / (i - g)117118def decomposition_capozza(ra: float, i: float, cout_conversion: float, rente_d: float, prime_croissance: float) -> dict:119    """Capozza–Helsley (1989) : P_L = ra/i + C + (r(d) - ra)/i + prime de croissance."""120    comp = {"valeur_agricole": ra / i, "cout_conversion": cout_conversion, "accessibilite": (rente_d - ra) / i, "prime_croissance": prime_croissance}121    comp["total"] = sum(comp.values()); return comp122123124# ----------------------------------------------------------------------------- rente résiduelle et levier125def valeur_residuelle(V: float, CN: float, depreciation: float) -> float:126    """V_L = V - (C_N - D) : valeur du terrain par résidu."""127    return V - (CN - depreciation)128129def levier_residuel(V: float, VL: float) -> float:130    """Élasticité de V_L par rapport à V : V / V_L."""131    return V / VL132133def elasticite_rente_prix(alpha: float) -> float:134    return 1.0 / alpha135136137# ----------------------------------------------------------------------------- capitalisation fiscale138def valeur_avec_taxe(R: float, i: float, tau: float) -> float:139    """V = R / (i + tau) : rente nette de taxe capitalisée (taxe proportionnelle à la valeur)."""140    return R / (i + tau)141142def capitalisation_taxe(V0: float, tau0: float, tau1: float, i: float, kappa: float = 1.0) -> dict:143    """Effet d'un changement de taux de taxe tau0 -> tau1 sur une valeur V0 établie sous tau0.144    exact : V1 = R/(i+tau1) avec R = V0 (i+tau0) ; approché : dV = -kappa * dT / i avec dT = (tau1-tau0) V0."""145    R = V0 * (i + tau0)146    V1 = R / (i + tau1)147    dT = (tau1 - tau0) * V0148    return {"R": R, "V1_exact": V1, "dV_exact": V1 - V0, "dT": dT, "dV_approx": -kappa * dT / i,149            "dV_exact_kappa": kappa * (V1 - V0), "dV_pct_exact": V1 / V0 - 1}150151def taux_effectif(tau_role: float, facteur: float) -> float:152    """Taux sur la valeur marchande : tau_eff = tau_role / F, où F est le facteur comparatif (V_marché = F × V_rôle)."""153    return tau_role / facteur154155def grille_capitalisation(V0: float, d_taux_pb: list, taux_cap: list, kappa: float = 1.0) -> pd.DataFrame:156    """Perte de valeur (en $) pour un écart de taux de taxe (points de base de la valeur) selon le taux de capitalisation."""157    rows = []158    for pb in d_taux_pb:159        row = {"ecart_pb": pb}160        for r in taux_cap:161            row[f"r={r:.0%}"] = -kappa * (pb / 10000.0) * V0 / r162        rows.append(row)163    return pd.DataFrame(rows).set_index("ecart_pb")164165166# ----------------------------------------------------------------------------- cas 1 : gradient observé167TRANCHES = [0, 2, 4, 6, 8, 10, 12, 20, 35]168ETIQUETTES = ["0-2", "2-4", "4-6", "6-8", "8-10", "10-12", "12-20", "20 et plus"]169170def tranches_distance(v: pd.DataFrame) -> pd.DataFrame:171    v = v.copy(); v["prix_m2"] = v["prix_vente"] / v["superficie_habitable_m2"]172    v["tranche"] = pd.cut(v["dist_centre_km"], TRANCHES, labels=ETIQUETTES, right=True)173    g = v.groupby("tranche", observed=True).agg(n=("prix_vente", "size"), dist_mediane=("dist_centre_km", "median"),174                                                 prix_median=("prix_vente", "median"), prix_m2_median=("prix_m2", "median"),175                                                 superficie_mediane=("superficie_habitable_m2", "median"),176                                                 terrain_median=("superficie_terrain_m2", "median"))177    g["secteur_dominant"] = v.groupby("tranche", observed=True)["secteur"].agg(lambda s: s.value_counts().index[0])178    return g179180def gradients_cas1(v: pd.DataFrame, modele: dict | None = None) -> dict:181    """Quatre spécifications du gradient prix/distance (coefficients en log par km)."""182    y = np.log(v["prix_vente"].astype(float).values)183    d = v["dist_centre_km"].astype(float).values184    out = {}185    # (a) brut : ln(P/S) = a - gamma d186    r = h.mco(np.column_stack([np.ones_like(d), d]), np.log(v["prix_vente"] / v["superficie_habitable_m2"]).values)187    out["brut_prix_m2"] = {"gamma": -r["beta"][1], "se": r["se_hc1"][1], "r2": r["r2"], "n": r["n"]}188    # (b) contrôlé : caractéristiques du modèle de référence sans secteurs, distance linéaire189    X = cas1.matrice(v).drop(columns=["dist_centre"] + [f"sect_{s}" for s in cas1.SECTEURS[1:]])190    X["dist"] = d191    r = h.mco(X.values, y)192    out["controle_lineaire"] = {"gamma": -r["beta"][-1], "se": r["se_hc1"][-1], "r2": r["r2"], "n": r["n"]}193    # (c) contrôlé, quadratique194    Xq = X.copy(); Xq["dist2"] = d ** 2 / 100.0195    r = h.mco(Xq.values, y)196    out["controle_quadratique"] = {"b1": r["beta"][-2], "b2_100": r["beta"][-1], "se1": r["se_hc1"][-2], "se2": r["se_hc1"][-1], "r2": r["r2"],197                                   "gamma_0": -r["beta"][-2], "gamma_10": -(r["beta"][-2] + 2 * r["beta"][-1] * 10 / 100.0)}198    # (d) contrôlé, aire urbaine contiguë (d <= 16 km : Hull, Gatineau, Aylmer)199    m = d <= 16.0200    r = h.mco(X.values[m], y[m])201    out["controle_urbain_16km"] = {"gamma": -r["beta"][-1], "se": r["se_hc1"][-1], "r2": r["r2"], "n": int(m.sum())}202    # (e) modèle de référence (secteurs + max(d-3, 0))203    modele = modele or cas1.hedonique(v)204    c = modele["coef"]205    out["reference"] = {"gamma_au_dela_3km": -float(c.loc["dist_centre", "coef"]), "se": float(c.loc["dist_centre", "se_hc1"]),206                        "t": float(c.loc["dist_centre", "t"]), "r2": modele["r2"],207                        "secteurs": {s: float(c.loc[f"sect_{s}", "coef"]) for s in cas1.SECTEURS[1:]},208                        "secteurs_pct": {s: float(c.loc[f"sect_{s}", "effet_pct"]) for s in cas1.SECTEURS[1:]}}209    # (f) gradient implicite entre secteurs : médianes par secteur vs distance médiane210    gs = v.groupby("secteur").agg(dist=("dist_centre_km", "median"), prix=("prix_vente", "median"), n=("prix_vente", "size"))211    out["secteurs_medianes"] = gs212    return out213214def gradient_exact_vs_exponentiel(P: dict = PARAMS) -> pd.DataFrame:215    d = np.array([0.0, 5.0, 10.0, 15.0, 20.0, 25.0])216    pe, pa = prix_logement(d, P), prix_exponentiel(d, P)217    return pd.DataFrame({"d_km": d, "p_exact": pe, "p_exponentiel": pa, "ecart_pct": pa / pe - 1, "q_m2": consommation_logement(d, P),218                         "rente_fonciere": rente_fonciere(d, P), "densite_relative": densite(d, P)})219220221# ----------------------------------------------------------------------------- cas 4 : décomposition interrégionale222def decomposition_rmr(pf: pd.DataFrame, reference: str = "Montréal") -> pd.DataFrame:223    """ln(V/n) = ln(RNE/n) - ln(R) par RMR, et écarts à la RMR de référence."""224    s = cas4.sommaire(pf)["par_rmr"].copy()225    s["rne_par_logement"] = s["rne"] / s["logements"]226    s["ln_V_n"] = np.log(s["valeur_par_logement"]); s["ln_RNE_n"] = np.log(s["rne_par_logement"]); s["ln_R"] = np.log(s["taux_pondere"])227    ref = s.loc[reference]228    s["ecart_ln_V_n"] = s["ln_V_n"] - ref["ln_V_n"]229    s["contrib_rne"] = s["ln_RNE_n"] - ref["ln_RNE_n"]230    s["contrib_taux"] = -(s["ln_R"] - ref["ln_R"])231    s["ratio_valeur"] = s["valeur_par_logement"] / ref["valeur_par_logement"]232    return s.sort_values("valeur_par_logement", ascending=False)233234235# ----------------------------------------------------------------------------- exemple intégral (impression)236def fmt(x, dec=0):237    s = f"{x:,.{dec}f}".replace(",", " ").replace(".", ",")238    return s239240def main():241    P = PARAMS242    print("=== 5.A  Modèle monocentrique simulé (paramètres pédagogiques déclarés) ===")243    print({k: v for k, v in P.items()})244    g = gradient_exponentiel(P); dbar = frontiere_urbaine(P)245    print(f"gradient exponentiel au centre gamma = t/(beta y) = {g:.5f} par km ({g*100:.2f} %/km) ; demi-distance ln2/gamma = {demi_distance(g):.1f} km")246    print(f"frontière urbaine d_bar (r = ra) = {dbar:.2f} km ; p(d_bar) = {prix_logement(dbar, P):.2f} $/m²/an ; élasticité rente/prix 1/alpha = {elasticite_rente_prix(P['alpha']):.3f}")247    print(f"valeur du terrain au centre r0/i = {fmt(valeur_terrain(P['r0'], P['i']))} $/m² ; à la frontière ra/i = {valeur_terrain(P['ra'], P['i']):.0f} $/m²")248    tab = gradient_exact_vs_exponentiel(P)249    print(tab.round(3).to_string(index=False))250    print(f"q(0) = {consommation_logement(0.0, P):.1f} m² ; q(10) = {consommation_logement(10.0, P):.1f} m² ; q(d_bar) = {consommation_logement(dbar, P):.1f} m²")251    print("\n--- Alonso : frontières entre usages (km) ---")252    (R1, t1), (R2, t2), (R3, t3) = ALONSO.values()253    f12, f23 = frontiere_usages(R1, t1, R2, t2), frontiere_usages(R2, t2, R3, t3)254    f3a = (R3 - P["ra"]) / t3255    print(f"commerces/dense : {f12:.2f} km ; dense/pavillonnaire : {f23:.2f} km ; pavillonnaire/agricole : {f3a:.2f} km")256    print("\n--- von Thünen (exercice 5.1) ---")257    for k, q in THUNEN.items():258        print(f"{k}: R0 = {q['Y']*(q['p']-q['c']):.0f} $/ha/an ; pente = {q['Y']*q['f']:.0f} $/ha/an/km ; distance limite = {distance_limite_thunen(**q):.2f} km")259    a, b = THUNEN.values()260    print(f"frontière maraîchage/grandes cultures : {frontiere_usages(a['Y']*(a['p']-a['c']), a['Y']*a['f'], b['Y']*(b['p']-b['c']), b['Y']*b['f']):.2f} km")261262    print("\n=== 5.B  Cas 1 (Gatineau, DONNÉES SYNTHÉTIQUES) : gradient prix/distance ===")263    d1 = cas1.charger(); v = d1["ventes"]; m = cas1.hedonique(v)264    tr = tranches_distance(v)265    print(tr.round(2).to_string())266    gr = gradients_cas1(v, m)267    for k in ["brut_prix_m2", "controle_lineaire", "controle_urbain_16km"]:268        r = gr[k]; print(f"{k}: gamma = {r['gamma']*100:.3f} %/km (e.-t. {r['se']*100:.3f}) ; R² = {r['r2']:.3f} ; n = {r['n']} ; demi-distance = {demi_distance(r['gamma']):.1f} km")269    q = gr["controle_quadratique"]270    print(f"controle_quadratique: b1 = {q['b1']*100:.3f} %/km (e.-t. {q['se1']*100:.3f}), b2/100 = {q['b2_100']*100:.4f} (e.-t. {q['se2']*100:.4f}) ; gamma(0) = {q['gamma_0']*100:.3f}, gamma(10 km) = {q['gamma_10']*100:.3f} %/km ; R² = {q['r2']:.3f}")271    rf = gr["reference"]272    print(f"reference (traite.cas1.hedonique) : dist_centre = -{rf['gamma_au_dela_3km']*100:.3f} %/km au-delà de 3 km (e.-t. {rf['se']*100:.3f}, t = {rf['t']:.2f}) ; R² = {rf['r2']:.3f}")273    print("  secteurs (réf. Hull), coef. et effet % :", {k: (round(v_, 4), round(rf['secteurs_pct'][k] * 100, 1)) for k, v_ in rf["secteurs"].items()})274    print(gr["secteurs_medianes"].round(2).to_string())275    gs = gr["secteurs_medianes"]276    pente = (math.log(gs.loc["Gatineau", "prix"]) - math.log(gs.loc["Hull", "prix"])) / (gs.loc["Gatineau", "dist"] - gs.loc["Hull", "dist"])277    print(f"gradient implicite Hull -> Gatineau (médianes) : {pente*100:.3f} %/km ; Hull -> Buckingham : {(math.log(gs.loc['Buckingham','prix'])-math.log(gs.loc['Hull','prix']))/(gs.loc['Buckingham','dist']-gs.loc['Hull','dist'])*100:.3f} %/km")278    print(f"sujet du cas 1 : dist_centre = {d1['sujet']['dist_centre_km']} km ; effet distance du modèle de référence = {max(d1['sujet']['dist_centre_km']-3,0)*rf['gamma_au_dela_3km']*100:.2f} %")279    # ce que vaut 1 km de plus pour le sujet selon la spécification contrôlée280    V_ind = 551029.0  # indication brute du ch. 16281    print(f"à partir de l'indication brute du ch. 16 ({fmt(V_ind)} $), 1 km de plus vaut {fmt(-V_ind*gr['controle_lineaire']['gamma'])} $ (spécification contrôlée) ; 5 km : {fmt(V_ind*(math.exp(-5*gr['controle_lineaire']['gamma'])-1))} $")282283    print("\n=== 5.C  Cas 4 (portefeuille, DONNÉES SYNTHÉTIQUES) : valeur par logement par RMR ===")284    d4 = cas4.charger(); dec = decomposition_rmr(d4["portefeuille"])285    cols = ["n", "logements", "valeur_par_logement", "rne_par_logement", "taux_pondere", "ratio_valeur", "ecart_ln_V_n", "contrib_rne", "contrib_taux"]286    print(dec[cols].round(4).to_string())287    for rmr in dec.index:288        if rmr != "Montréal":289            e = dec.loc[rmr]; part = e["contrib_rne"] / e["ecart_ln_V_n"] if e["ecart_ln_V_n"] != 0 else float("nan")290            print(f"  {rmr}: écart ln = {e['ecart_ln_V_n']:.3f} dont RNE/log. {e['contrib_rne']:.3f} ({part*100:.0f} %) et taux {e['contrib_taux']:.3f} ({(1-part)*100:.0f} %)")291292    print("\n=== 5.D  Rente résiduelle et levier ===")293    CN = 397567.33; dep = 0.30 * CN; V = 551029.0294    VL = valeur_residuelle(V, CN, dep)295    print(f"C_N (ch. 21) = {fmt(CN,2)} $ ; dépréciation hypothétique 30 % = {fmt(dep,2)} $ ; bâtiment déprécié = {fmt(CN-dep,2)} $")296    print(f"V (indication brute ch. 16) = {fmt(V)} $ -> V_L = {fmt(VL)} $ ({VL/V*100:.1f} % de V) ; levier V/V_L = {levier_residuel(V, VL):.2f}")297    for dv in (-0.05, 0.05, -0.10):298        VL2 = valeur_residuelle(V * (1 + dv), CN, dep)299        print(f"  V {dv:+.0%} -> V_L = {fmt(VL2)} $ ({VL2/VL-1:+.1%})")300    print(f"élasticité de la rente foncière au prix du logement (alpha = {P['alpha']}) : {elasticite_rente_prix(P['alpha']):.2f} ; +10 % de prix -> rente x {1.1**elasticite_rente_prix(P['alpha']):.3f}")301    cz = decomposition_capozza(P["ra"], P["i"], 25.0, rente_fonciere(10.0, P), 60.0)302    print("Capozza-Helsley à 10 km ($/m²) :", {k: round(v_, 1) for k, v_ in cz.items()})303304    print("\n=== 5.E  Capitalisation de la taxe foncière ===")305    for k, t_ in TAUX_2026.items():306        print(f"  {k}: {t_:.5f} $/100 $ = {t_/100*10000:.1f} pb de la valeur")307    V0 = 500000.0; i = 0.05308    for pb in (10, 22.4, 50):309        c = capitalisation_taxe(V0, 0.0070, 0.0070 + pb / 10000, i)310        print(f"  écart de {pb} pb sur {fmt(V0)} $ : dT = {fmt(c['dT'])} $/an ; dV approché = {fmt(c['dV_approx'])} $ ; dV exact = {fmt(c['dV_exact'])} $ ({c['dV_pct_exact']*100:.2f} %) ; à 60 % : {fmt(0.6*c['dV_approx'])} $")311    ecart_qc_gat = (TAUX_2026["Québec (1 à 5 logements)"] - TAUX_2026["Gatineau (taux de base)"]) / 100312    print(f"écart nominal Québec (1-5 log.) - Gatineau (base) = {ecart_qc_gat*10000:.1f} pb ; sur {fmt(V0)} $ : {fmt(ecart_qc_gat*V0)} $/an ; capitalisé à 5 % : {fmt(ecart_qc_gat*V0/i)} $ ; à 60 % : {fmt(0.6*ecart_qc_gat*V0/i)} $")313    for ville, F in FACTEUR_2026.items():314        key = "Gatineau (taux de base)" if ville == "Gatineau" else "Montréal (taxe foncière générale, hors arrondissement)"315        print(f"  taux effectif {ville} : {TAUX_2026[key]:.4f}/{F} = {taux_effectif(TAUX_2026[key], F):.4f} $/100 $ de valeur marchande")316    print(f"  Québec avec un facteur hypothétique de 1,10 : {taux_effectif(TAUX_2026['Québec (1 à 5 logements)'], 1.10):.4f} $/100 $")317    sujet_role = d1["sujet"]["valeur_role_2025"]318    T_gat = sujet_role * TAUX_2026["Gatineau (taux de base)"] / 100; T_sco = sujet_role * TAUX_2026["Taxe scolaire 2026-2027 (toute la province)"] / 100319    print(f"sujet du cas 1 (rôle fictif {fmt(sujet_role)} $) : taxe générale au taux de base Gatineau = {fmt(T_gat)} $ ; scolaire (sans l'exemption de 25 000 $) = {fmt(T_sco)} $")320    print(f"  écart 6+ vs 1-5 logements à Québec : {(TAUX_2026['Québec (6 logements et plus)']-TAUX_2026['Québec (1 à 5 logements)'])*100:.1f} pb ; sur un immeuble de 2 000 000 $ : {fmt((TAUX_2026['Québec (6 logements et plus)']-TAUX_2026['Québec (1 à 5 logements)'])/100*2e6)} $/an ; capitalisé à 5,5 % : {fmt((TAUX_2026['Québec (6 logements et plus)']-TAUX_2026['Québec (1 à 5 logements)'])/100*2e6/0.055)} $")321    G = grille_capitalisation(V0, [5, 10, 25, 50, 100], [0.04, 0.05, 0.06, 0.07])322    print("grille (kappa = 1, V0 = 500 000 $) :\n", G.round(0).to_string())323    # coût d'usage : part de la taxe324    tau_eff = taux_effectif(TAUX_2026["Gatineau (taux de base)"], 1.02) / 100 + TAUX_2026["Taxe scolaire 2026-2027 (toute la province)"] / 100325    print(f"taux effectif total Gatineau (base + scolaire) ≈ {tau_eff*100:.4f} $/100 $ ; part de la taxe dans un coût d'usage de (i=4,12 %, tau, delta=1,5 %, g=2 %) : {tau_eff/(0.0412+tau_eff+0.015-0.02)*100:.1f} %")326327if __name__ == "__main__":328    main()329
Files · ac7e5213a2af3676c22c1500a18fcfe86d2041bc
Menu

Theme