SPB Git
3.7 KB · 52 lines python
1"""Cas 2 — triplex à Québec, Limoilou (données synthétiques) : calculs PARTAGÉS par les chapitres 26, 27, 32 et 46.2Revenus, dépenses, RNE, multiplicateurs et taux extraits des ventes de plex, comparaison par logement."""3from __future__ import annotations4import json, pathlib5import numpy as np, pandas as pd6from . import finance as fin78ROOT = pathlib.Path(__file__).resolve().parents[3]9D = ROOT / "data/synthetic"10TAUX_VACANCE_PERTES = 0.03      # convention du dossier : inoccupation 2,4 % (SCHL Québec) + mauvaises créances ≈ 0,6 %11GESTION_IMPUTEE = 0.04          # propriétaire-occupant : gestion imputée à 4 % du RBE1213def charger() -> dict:14    return {"ventes": pd.read_csv(D / "cas2_quebec_ventes_plex.csv", parse_dates=["date_vente"]), "sujet": json.loads((D / "cas2_quebec_sujet.json").read_text()),15            "loyers": pd.read_csv(D / "cas2_quebec_loyers.csv"), "depenses": pd.read_csv(D / "cas2_quebec_depenses.csv")}1617def revenus(loyers: pd.DataFrame) -> dict:18    rbp_c = float(loyers["loyer_mensuel"].sum() * 12); rbp_m = float(loyers["loyer_marche_estime"].sum() * 12)19    rbe = fin.revenu_brut_effectif(rbp_c, TAUX_VACANCE_PERTES)20    return {"rbp_contrat": rbp_c, "rbp_marche": rbp_m, "ecart_marche": rbp_m - rbp_c, "taux_vacance_pertes": TAUX_VACANCE_PERTES, "rbe": rbe,21            "rbe_marche": fin.revenu_brut_effectif(rbp_m, TAUX_VACANCE_PERTES)}2223def depenses_normalisees(dep: pd.DataFrame, rbe: float) -> pd.DataFrame:24    d = dep.copy(); d["normalise"] = d["montant_annuel"].astype(float)25    d.loc[d["type"] == "imputée", "normalise"] = GESTION_IMPUTEE * rbe26    return d2728def rne(loyers, dep) -> dict:29    r = revenus(loyers); dn = depenses_normalisees(dep, r["rbe"])30    reserve = float(dn.loc[dn["type"] == "réserve", "normalise"].sum()); autres = float(dn.loc[dn["type"] != "réserve", "normalise"].sum())31    noi = fin.rne(r["rbe"], autres, reserve)32    return {**r, "depenses_hors_reserve": autres, "reserve": reserve, "depenses_totales": autres + reserve, "rne": noi,33            "ratio_depenses": (autres + reserve) / r["rbe"], "detail": dn}3435def comparables_plex(v: pd.DataFrame, s: dict, nb_log=(2, 3), fenetre_mois: int = 24, quartiers=("Limoilou", "Saint-Sauveur", "Saint-Roch", "Maizerets"), date_eval="2026-09-01") -> pd.DataFrame:36    c = v[(v["nb_logements"].isin(nb_log)) & (v["quartier"].isin(quartiers)) & (v["date_vente"] >= pd.Timestamp(date_eval) - pd.DateOffset(months=fenetre_mois))].copy()37    c["mois"] = (pd.Timestamp(date_eval) - c["date_vente"]).dt.days / 30.437538    return c3940def indicateurs_marche(c: pd.DataFrame) -> dict:41    tri = c[c["nb_logements"] == 3]42    return {"n": len(c), "n_triplex": len(tri), "mrb_median": float(c["mrb"].median()), "mrb_median_triplex": float(tri["mrb"].median()) if len(tri) else np.nan,43            "taux_median": float(c["taux_global"].median()), "taux_q1": float(c["taux_global"].quantile(0.25)), "taux_q3": float(c["taux_global"].quantile(0.75)),44            "prix_par_logement_median": float(c["prix_par_logement"].median()), "prix_par_logement_triplex": float(tri["prix_par_logement"].median()) if len(tri) else np.nan,45            "prix_m2_median": float((c["prix_vente"] / c["superficie_habitable_m2"]).median())}4647def tout() -> dict:48    d = charger(); base = rne(d["loyers"], d["depenses"]); c = comparables_plex(d["ventes"], d["sujet"]); ind = indicateurs_marche(c)49    v_mrb = ind["mrb_median_triplex"] * base["rbp_contrat"]; v_cap = fin.capitalisation_directe(base["rne"], ind["taux_median"])50    v_log = ind["prix_par_logement_triplex"] * d["sujet"]["nb_logements"]51    return {**d, "base": base, "comparables": c, "indicateurs": ind, "valeur_mrb": v_mrb, "valeur_capitalisation": v_cap, "valeur_par_logement": v_log}52
Files · 0b0a78499997700c9aa16d44b91243a1ef46c0bb
Menu

Theme