SPB Git
23.7 KB · 327 lines python
1#!/usr/bin/env python32"""Chapitre 42 — Copropriétés et conversions : exemples chiffrés (DONNÉES SYNTHÉTIQUES, graine 20261031).34Réutilise l'immeuble fictif de huit fractions du chapitre 7 (analysis/python/cases/ch07_servitudes_copro.py : quotes-parts,5répartition des charges, cotisation de seuil, déficit de capitalisation) et y ajoute quatre blocs, tous générés ici6(aucun fichier externe ; aucune statistique de marché n'est reproduite) :7  A. Évaluation de la fraction F4 par comparaison : deux ventes dans l'immeuble, trois ventes dans d'autres immeubles fictifs ;8     ajustements de date, d'étage, d'orientation, de stationnement, de superficie, de charges courantes (capitalisation) et de9     situation financière du syndicat (déficit du fonds de prévoyance, cotisation spéciale) ; réconciliation (traite.comparables).10  B. Effet d'une cotisation spéciale égale au déficit de capitalisation, répartie selon les quotes-parts (figure).11  C. Copropriété locative : revenu net d'exploitation et taux implicite d'une fraction louée (traite.finance).12  D. Conversion d'un immeuble locatif de huit logements en copropriété divise : valeur en bloc (capitalisation directe) contre13     somme des fractions nette des coûts, des délais et des locataires (rotation, préemption, vente occupée) ; sensibilités.14Toutes les fonctions financières viennent de analysis/python/traite/ (finance.py, comparables.py) ; rien n'est réécrit.15Usage (racine du dépôt) : PYTHONPATH=analysis/python python3 analysis/python/cases/ch42_copro.py16"""17from __future__ import annotations18import sys, math, pathlib19import numpy as np20HERE = pathlib.Path(__file__).resolve()21sys.path.insert(0, str(HERE.parents[1])); sys.path.insert(0, str(HERE.parent))22from traite import comparables as cp23from traite import finance as fin24import ch07_servitudes_copro as c72526SEED = 2026103127DATE_EVAL = "2026-09-01"2829# ----------------------------------------------------------------------------- marché fictif : paramètres déclarés30# Prix implicites « du marché fictif » (hypothèses pédagogiques ; l'évaluateur les tient d'une analyse de ventes appariées / hédonique).31PRIX_M2_REF = 3_650.0            # $/m² pour une fraction de 98 m², 2e étage, orientation est/ouest, sans stationnement, immeuble bien capitalisé32ELASTICITE_S = 0.60              # élasticité prix-superficie (< 1) ; prix marginal au point de référence = 0,60 × 3 650 = 2 190 $/m²33S_REF = 98.034COEF_ETAGE_MARCHE = {"RDC": -0.04, "2e": 0.00, "3e": 0.02}     # proportion du prix (immeubles sans ascenseur)35COEF_ORIENT_MARCHE = {"nord": -0.03, "est": 0.00, "ouest": 0.00, "sud": 0.03}36STATIONNEMENT = 28_000.0         # $ ; case intérieure à usage restreint37G_MARCHE = 0.02                  # croissance annuelle des prix des copropriétés sur le marché fictif (ajustement de conditions du marché)38R_CHARGES = 0.08                 # taux de capitalisation des écarts de charges courantes (hypothèse pédagogique ; à extraire du marché)39KAPPA = 1.0                      # coefficient de capitalisation du passif du syndicat (1 = dollar pour dollar, convention du ch. 7)40CHARGES_REF_M2 = 40_000.0 / 710.0   # 56,34 $/m²/an : charges courantes de l'immeuble fictif (hors fonds), référence du marché41SIGMA_BRUIT = 0.02               # bruit de transaction (log) dans la génération des prix des comparables4243# Situation financière des syndicats fictifs : déficit de capitalisation (ch. 7) ou cotisation spéciale votée non payée44QP = c7.quotes_parts()45INC = c7.incidence_valeur(QP)                         # déficit de l'immeuble fictif du ch. 7 : 150 908 $46DEFICIT_IMMEUBLE = float(INC["deficit"])47Q_F4 = float(QP["q"][3]); Q_F2 = float(QP["q"][1]); Q_F7 = float(QP["q"][6])4849SUJET = dict(nom="F4 (sujet)", immeuble="A (fictif, 8 fractions)", S=98.0, etage="2e", orient="sud", stat=True,50             charges_m2=CHARGES_REF_M2, passif=Q_F4 * DEFICIT_IMMEUBLE, mois=0.0)5152COMPARABLES = [  # (nom, immeuble, S, étage, orientation, stationnement, charges courantes $/m²/an, passif de la fraction ($), ancienneté (mois), date)53    dict(nom="C1 = F2", immeuble="A (même immeuble)", S=98.0, etage="RDC", orient="sud", stat=True, charges_m2=CHARGES_REF_M2, passif=Q_F2 * DEFICIT_IMMEUBLE, mois=6.0, date="2026-03-02"),54    dict(nom="C2 = F7", immeuble="A (même immeuble)", S=85.0, etage="2e", orient="est", stat=True, charges_m2=CHARGES_REF_M2, passif=Q_F7 * DEFICIT_IMMEUBLE, mois=10.0, date="2025-11-03"),55    dict(nom="C3", immeuble="B (24 fractions, ascenseur)", S=96.0, etage="2e", orient="sud", stat=True, charges_m2=78.0, passif=0.0, mois=3.0, date="2026-06-01"),56    dict(nom="C4", immeuble="C (12 fractions)", S=100.0, etage="3e", orient="ouest", stat=False, charges_m2=52.0, passif=0.09 * 60_000.0, mois=4.0, date="2026-05-04"),57    dict(nom="C5", immeuble="D (8 fractions)", S=92.0, etage="RDC", orient="sud", stat=True, charges_m2=58.0, passif=0.13 * 240_000.0, mois=8.0, date="2026-01-05"),58]596061def valeur_modele(u: dict) -> float:62    """Valeur « vraie » d'une fraction dans le marché fictif à la date d'évaluation (hypothèse de génération).63    V = base(S) × (1 + c_étage + c_orient) + stationnement − κ × passif − (c_m² − c_ref) × S / R_c."""64    base = PRIX_M2_REF * S_REF * (u["S"] / S_REF) ** ELASTICITE_S65    v = base * (1.0 + COEF_ETAGE_MARCHE[u["etage"]] + COEF_ORIENT_MARCHE[u["orient"]])66    v += STATIONNEMENT if u["stat"] else 0.067    v -= KAPPA * u["passif"]68    v -= (u["charges_m2"] - CHARGES_REF_M2) * u["S"] / R_CHARGES69    return v707172def generer_prix(comparables=COMPARABLES, seed: int = SEED) -> list[dict]:73    """Prix observés = valeur du modèle déflatée à la date de vente (croissance g) × bruit, arrondis à 500 $."""74    rng = np.random.default_rng(seed)75    out = []76    for c in comparables:77        v = valeur_modele(c)78        p = v * (1.0 + G_MARCHE) ** (-c["mois"] / 12.0) * math.exp(rng.normal(0.0, SIGMA_BRUIT))79        d = dict(c); d["prix"] = float(np.round(p / 500.0) * 500.0); d["valeur_modele"] = v80        out.append(d)81    return out828384def prix_marginal_m2(S: float = S_REF) -> float:85    """Prix implicite marginal d'un m² au voisinage de la superficie du sujet : dV/dS = ε × base(S) / S."""86    base = PRIX_M2_REF * S_REF * (S / S_REF) ** ELASTICITE_S87    return ELASTICITE_S * base / S888990def ajuster(comp: dict, sujet: dict = SUJET, r_charges: float = R_CHARGES, kappa: float = KAPPA, g: float = G_MARCHE) -> cp.ComparableAjuste:91    """Grille : ajustement de conditions du marché (%, séquentiel) puis ajustements de propriété en dollars, signe du point de vue du sujet."""92    p_t = comp["prix"] * (1.0 + g) ** (comp["mois"] / 12.0)          # prix ramené à la date d'évaluation93    A = {"marché": p_t - comp["prix"]}94    A["étage"] = p_t * (COEF_ETAGE_MARCHE[sujet["etage"]] - COEF_ETAGE_MARCHE[comp["etage"]])95    A["orientation"] = p_t * (COEF_ORIENT_MARCHE[sujet["orient"]] - COEF_ORIENT_MARCHE[comp["orient"]])96    A["stationnement"] = STATIONNEMENT * (int(sujet["stat"]) - int(comp["stat"]))97    A["superficie"] = prix_marginal_m2() * (sujet["S"] - comp["S"])98    A["charges"] = (comp["charges_m2"] - sujet["charges_m2"]) * sujet["S"] / r_charges99    A["syndicat"] = kappa * (comp["passif"] - sujet["passif"])100    return cp.ComparableAjuste(comp["nom"], comp["prix"], A)101102103def grille_fraction(r_charges: float = R_CHARGES, kappa: float = KAPPA, g: float = G_MARCHE, poids: str = "inverse_brut") -> dict:104    comps = generer_prix()105    ajustes = [ajuster(c, SUJET, r_charges, kappa, g) for c in comps]106    w = cp.poids_inverse_brut(ajustes) if poids == "inverse_brut" else None107    rec = cp.reconciliation(ajustes, w)108    return {"comparables": comps, "ajustes": ajustes, "reconciliation": rec, "poids": rec["poids"],109            "valeur_modele_sujet": valeur_modele(SUJET), "prix_marginal_m2": prix_marginal_m2()}110111112def sensibilite_fraction(r_charges_list=(0.05, 0.06, 0.08, 0.10, 0.12), kappas=(0.0, 0.5, 0.75, 1.0)) -> dict:113    grille = {k: [grille_fraction(r, k)["reconciliation"]["valeur_ponderee"] for r in r_charges_list] for k in kappas}114    return {"r_charges": list(r_charges_list), "kappas": list(kappas), "grille": grille}115116117# ----------------------------------------------------------------------------- B. cotisation spéciale118def cotisation_speciale(montant: float | None = None, qp: dict = QP) -> dict:119    """Répartition d'une cotisation spéciale (par défaut égale au déficit de capitalisation du ch. 7) selon les quotes-parts ;120    comparaison avec les charges annuelles normalisées (cotisation de seuil) de chaque fraction."""121    montant = DEFICIT_IMMEUBLE if montant is None else montant122    A_star = c7.cotisation_seuil()123    rep = c7.repartition_charges(A_star, qp)124    part = qp["q"] * montant125    return {"montant": montant, "par_fraction": part, "charges_annuelles": rep["par_fraction_total"],126            "ratio_annees": part / rep["par_fraction_total"], "A_star": A_star, "rep": rep}127128129# ----------------------------------------------------------------------------- C. copropriété locative (F4 louée)130LOYER_F4 = 1_700.0        # $/mois ; hypothèse pédagogique dans l'ordre de grandeur des loyers SCHL (oct. 2025) des copropriétés offertes en location, 2 ch.131INOCC_COPRO = 0.021       # SCHL, RMR de Montréal, copropriétés offertes en location, octobre 2025 (tableau 4.1.1)132TAXES_F4 = 3_300.0        # $/an ; ordre de grandeur du compte de taxes moyen d'une copropriété à Montréal en 2026 (La Presse, 2026-10-19, source secondaire)133ASSURANCE_COPRO = 600.0   # $/an ; hypothèse (BAC 2022 : 544 $ pour un copropriétaire, partiellement vérifié)134GESTION = 0.05            # part du revenu brut effectif (forfait usuel)135TAUX_PLEX = 0.0525        # taux global de référence des petits immeubles locatifs du marché fictif (dans la fourchette CBRE Québec Low Rise B T3 2026 : 5,00-6,00 %)136137138def copro_locative(valeur: float, loyer: float = LOYER_F4, inocc: float = INOCC_COPRO, taxes: float = TAXES_F4,139                   assurance: float = ASSURANCE_COPRO, gestion: float = GESTION, frais_copro: float | None = None) -> dict:140    """RNE d'une fraction louée (convention de l'ouvrage : la cotisation au fonds de prévoyance normalisée tient lieu de réserve)."""141    if frais_copro is None:142        frais_copro = float(c7.repartition_charges(c7.cotisation_seuil(), QP)["par_fraction_total"][3])   # F4 : 10 218 $143    rbp = loyer * 12.0144    rbe = fin.revenu_brut_effectif(rbp, inocc)145    de = taxes + assurance + frais_copro + gestion * rbe146    noi = fin.rne(rbe, de)147    fonds = float(c7.repartition_charges(c7.cotisation_seuil(), QP)["par_fraction_fonds"][3])   # part « fonds de prévoyance » des frais (3 082 $)148    return {"rbp": rbp, "rbe": rbe, "depenses": de, "frais_copro": frais_copro, "noi": noi, "noi_sans_reserve": noi + fonds, "fonds": fonds,149            "taux_implicite_sans_reserve": fin.taux_global_extrait(noi + fonds, valeur),150            "taux_implicite": fin.taux_global_extrait(noi, valeur), "ratio_depenses": fin.ratio_depenses(de, rbe),151            "valeur_investissement_5_25": fin.capitalisation_directe(noi, TAUX_PLEX),152            "mrb": fin.multiplicateur_revenu_brut(valeur, rbp)}153154155# ----------------------------------------------------------------------------- D. conversion locatif → copropriété divise156# L'immeuble fictif de huit fractions, vu AVANT conversion comme un immeuble locatif de 8 logements (hypothèses déclarées).157LOYERS_LOG = {"F1": 1_250.0, "F2": 1_650.0, "F3": 1_300.0, "F4": 1_700.0, "F5": 1_450.0, "F6": 1_850.0, "F7": 1_500.0, "F8": 1_525.0}   # $/mois, loyers contractuels158INOCC_LOCATIF = 0.03          # inoccupation + mauvaises créances (hypothèse ; SCHL RMR 2025 : 2,4 % Québec, 2,9 % Montréal, 3,8 % Gatineau)159RATIO_DEPENSES_BLOC = 0.42    # dépenses d'exploitation + réserve / RBE (hypothèse pédagogique pour un immeuble de 8 logements des années 1990)160LOCATAIRES = {"F1": True, "F2": False, "F3": True, "F4": False, "F5": True, "F6": True, "F7": True, "F8": False}  # occupé par un locataire (bail en cours) ?161PREEMPTION = {"F6"}           # locataire(s) qui acceptent l'offre de vente (art. 54.7) à la valeur libre162ROTATION = 0.12               # part des logements occupés libérés chaque année (départ volontaire ; aucune éviction : moratoire jusqu'au 6 juin 2027)163DECOTE_OCCUPE = 0.20          # décote d'une fraction vendue occupée (bail maintenu, art. 1937 C.c.Q.) à un investisseur — hypothèse164R_PROMOTEUR = 0.10            # taux d'actualisation exigé par le convertisseur165FRAIS_VENTE = 0.04            # courtage et frais de vente (proportion du prix) — hypothèse166COUTS_CONVERSION = {          # dollars de 2026, hypothèses pédagogiques167    "Cadastre vertical et certificats de localisation (arpenteur-géomètre)": 28_000.0,168    "Déclaration de copropriété, publication et honoraires juridiques": 22_000.0,169    "Rapport d'expert et circulaire d'information (art. 54.6 Loi TAL)": 9_000.0,170    "Frais de demande (TAL, municipalité), budget prévisionnel, valeur relative": 6_000.0,171    "Individualisation des services et mise aux normes (8 logements)": 120_000.0,172    "Rafraîchissement des parties communes": 35_000.0,173    "Dotation initiale du fonds de prévoyance (solde d'ouverture requis, ch. 7)": 85_000.0,174}175HORIZON_CONV = 6              # années de projection des ventes176PORTAGE_PCT = 0.015           # coûts de portage annuels (taxes, assurance, intérêts nets des loyers résiduels) en proportion de la valeur des fractions invendues — hypothèse177178179def unites_fictives() -> list[dict]:180    """Les huit fractions de l'immeuble du ch. 7 comme unités du marché fictif (charges de référence, passif nul après dotation du fonds)."""181    out = []182    for i, (n, S, e, o, st) in enumerate(c7.FRACTIONS):183        out.append(dict(nom=n, S=S, etage=e, orient=o, stat=st, charges_m2=CHARGES_REF_M2, passif=0.0, loyer=LOYERS_LOG[n],184                        locataire=LOCATAIRES[n], preemption=n in PREEMPTION, q=float(QP["q"][i])))185    return out186187188def valeur_bloc(taux: float = TAUX_PLEX, inocc: float = INOCC_LOCATIF, ratio_dep: float = RATIO_DEPENSES_BLOC) -> dict:189    u = unites_fictives()190    rbp = 12.0 * sum(x["loyer"] for x in u)191    rbe = fin.revenu_brut_effectif(rbp, inocc)192    de = ratio_dep * rbe193    noi = fin.rne(rbe, de)194    return {"rbp": rbp, "rbe": rbe, "depenses": de, "noi": noi, "taux": taux, "valeur": fin.capitalisation_directe(noi, taux),195            "mrb": fin.multiplicateur_revenu_brut(fin.capitalisation_directe(noi, taux), rbp), "n": len(u)}196197198def calendrier_ventes(rotation: float = ROTATION, decote_occ: float = DECOTE_OCCUPE, vendre_occupe: bool = False,199                      horizon: int = HORIZON_CONV, unites=None) -> dict:200    """Produit brut attendu par année (fin d'année), avant frais de vente.201    An 1 : fractions libres et locataires qui exercent la préemption, à la valeur libre. Fractions occupées : si `vendre_occupe`,202    vendues à l'an 1 avec décote ; sinon vendues libres l'année où le locataire part (probabilité ρ par an), le reliquat à l'horizon203    étant vendu occupé avec décote. Valeurs libres = modèle du marché fictif (passif nul après dotation du fonds)."""204    u = unites or unites_fictives()205    produit = np.zeros(horizon + 1); invendu = np.zeros(horizon + 1)206    valeurs = {x["nom"]: valeur_modele(x) for x in u}207    libres_an1 = [x for x in u if (not x["locataire"]) or x["preemption"]]208    occupes = [x for x in u if x["locataire"] and not x["preemption"]]209    produit[1] += sum(valeurs[x["nom"]] for x in libres_an1)210    V_occ = sum(valeurs[x["nom"]] for x in occupes)211    if vendre_occupe:212        produit[1] += V_occ * (1.0 - decote_occ)213    else:214        reste = 1.0215        for t in range(1, horizon + 1):216            if t < horizon:217                produit[t] += V_occ * reste * rotation      # fractions libérées pendant l'année t, vendues libres en fin d'année218                reste *= (1.0 - rotation)219            else:220                produit[t] += V_occ * reste * (1.0 - decote_occ)   # reliquat vendu occupé à l'horizon221                reste = 0.0222            invendu[t] = V_occ * reste223    # valeur libre des fractions invendues au début de chaque année (assiette des coûts de portage)224    base_portage = np.zeros(horizon + 1)225    total = sum(valeurs.values())226    base_portage[1] = total227    if not vendre_occupe:228        reste = 1.0229        for t in range(2, horizon + 1):230            reste *= (1.0 - rotation)231            base_portage[t] = V_occ * reste232    return {"produit": produit, "valeurs": valeurs, "somme_libre": total, "V_occ": V_occ, "n_libres_an1": len(libres_an1),233            "n_occupes": len(occupes), "base_portage": base_portage}234235236def faisabilite(rotation: float = ROTATION, decote_occ: float = DECOTE_OCCUPE, r: float = R_PROMOTEUR, vendre_occupe: bool = False,237                frais_vente: float = FRAIS_VENTE, couts=COUTS_CONVERSION, portage: float = PORTAGE_PCT, horizon: int = HORIZON_CONV) -> dict:238    """Valeur de conversion V_conv = Σ_t [produit_t (1 − f) − portage_t] / (1+r)^t − coûts de conversion (t = 0) ; prime = V_conv − V_bloc."""239    cal = calendrier_ventes(rotation, decote_occ, vendre_occupe, horizon)240    flux = np.zeros(horizon + 1)241    flux[0] = -sum(couts.values())242    for t in range(1, horizon + 1):243        flux[t] = cal["produit"][t] * (1.0 - frais_vente) - portage * cal["base_portage"][t]244    v_conv = fin.van(list(flux), r)245    bloc = valeur_bloc()246    return {"flux": flux, "v_conv": v_conv, "v_bloc": bloc["valeur"], "prime": v_conv - bloc["valeur"],247            "somme_libre": cal["somme_libre"], "couts": sum(couts.values()), "frais_vente_total": float(sum(cal["produit"]) * frais_vente),248            "portage_total": float(sum(portage * cal["base_portage"][1:])), "produit": cal["produit"], "calendrier": cal, "bloc": bloc,249            "marge_sur_bloc": (v_conv - bloc["valeur"]) / bloc["valeur"],250            "somme_nette_non_actualisee": float(sum(cal["produit"]) * (1 - frais_vente) - sum(couts.values()) - sum(portage * cal["base_portage"][1:]))}251252253def sensibilite_conversion(rotations=(0.05, 0.08, 0.12, 0.20, 0.30), decotes=(0.10, 0.20, 0.30), r: float = R_PROMOTEUR) -> dict:254    grille = {d: [faisabilite(rho, d, r)["v_conv"] for rho in rotations] for d in decotes}255    occ = {d: faisabilite(ROTATION, d, r, vendre_occupe=True)["v_conv"] for d in decotes}256    return {"rotations": list(rotations), "decotes": list(decotes), "grille": grille, "vente_occupee_an1": occ, "v_bloc": valeur_bloc()["valeur"]}257258259def seuil_prix(r: float = R_PROMOTEUR) -> float:260    """Facteur multiplicatif uniforme des valeurs libres pour lequel V_conv = V_bloc (point mort de la conversion)."""261    global PRIX_M2_REF262    p0 = PRIX_M2_REF263    lo, hi = 0.3 * p0, 1.5 * p0264    for _ in range(100):265        mid = 0.5 * (lo + hi); PRIX_M2_REF = mid266        if faisabilite(r=r)["prime"] > 0: hi = mid267        else: lo = mid268    PRIX_M2_REF = p0269    return hi / p0270271272# ----------------------------------------------------------------------------- impression273def fmt(x): return f"{x:,.0f}".replace(",", " ")274275276def main() -> dict:277    out = {}278    g = grille_fraction(); out["grille"] = g279    print(f"== A. Fraction F4 (sujet) — marché fictif, graine {SEED}, date d'évaluation {DATE_EVAL}")280    print(f"  passif du syndicat A (déficit {fmt(DEFICIT_IMMEUBLE)} $) : part de F4 = q4 {Q_F4:.4f} × déficit = {fmt(SUJET['passif'])} $ ; prix marginal {g['prix_marginal_m2']:,.0f} $/m²")281    print(f"  valeur du modèle pour F4 (contrôle de génération) : {fmt(g['valeur_modele_sujet'])} $")282    for c, a, w in zip(g["comparables"], g["ajustes"], g["poids"]):283        print(f"  {c['nom']:8s} {c['immeuble']:28s} S {c['S']:5.0f} {c['etage']:3s} {c['orient']:5s} stat {int(c['stat'])} charges {c['charges_m2']:5.1f} $/m² passif {fmt(c['passif']):>7} $ "284              f"{c['mois']:4.0f} mois prix {fmt(c['prix']):>8} $ (modèle {fmt(c['valeur_modele'])})")285        print("           ajustements : " + ", ".join(f"{k} {v:+,.0f}" for k, v in a.ajustements.items()).replace(",", " ").replace(" $", "$"))286        print(f"           prix ajusté {fmt(a.prix_ajuste)} $ ; brut {a.brut_pct:.1%} ; net {a.net_pct:+.1%} ; poids {w:.3f}")287    rec = g["reconciliation"]288    print(f"  réconciliation : pondérée {fmt(rec['valeur_ponderee'])} $ ; moyenne {fmt(rec['moyenne'])} ; médiane {fmt(rec['mediane'])} ; min {fmt(rec['min'])} ; max {fmt(rec['max'])} ; étendue {rec['etendue_pct']:.1%}")289    sf = sensibilite_fraction(); out["sens_fraction"] = sf290    print("  sensibilité (valeur pondérée) : lignes κ, colonnes R_c " + "  ".join(f"{r:.0%}" for r in sf["r_charges"]))291    for k, row in sf["grille"].items():292        print(f"    κ={k:.2f} " + "  ".join(f"{v:9,.0f}" for v in row).replace(",", " "))293    cs = cotisation_speciale(); out["cotisation"] = cs294    print(f"\n== B. Cotisation spéciale de {fmt(cs['montant'])} $ (déficit du ch. 7) répartie selon q_i ; charges annuelles normalisées (A* = {fmt(cs['A_star'])} $)")295    for n, p, ch, r in zip(QP["noms"], cs["par_fraction"], cs["charges_annuelles"], cs["ratio_annees"]):296        print(f"  {n} : cotisation {fmt(p):>7} $ ; charges {fmt(ch):>6} $/an ; = {r:.2f} années de charges")297    v_f4 = rec["valeur_ponderee"]298    cl = copro_locative(v_f4); out["locative"] = cl299    print(f"\n== C. F4 louée {fmt(LOYER_F4)} $/mois : RBP {fmt(cl['rbp'])} ; RBE {fmt(cl['rbe'])} ; dépenses {fmt(cl['depenses'])} (frais copro {fmt(cl['frais_copro'])}) ; RNE {fmt(cl['noi'])} $")300    print(f"  RNE hors cotisation au fonds ({fmt(cl['fonds'])} $) = {fmt(cl['noi_sans_reserve'])} $ → taux {cl['taux_implicite_sans_reserve']:.2%}")301    print(f"  taux implicite sur {fmt(v_f4)} $ = {cl['taux_implicite']:.2%} ; ratio de dépenses {cl['ratio_depenses']:.1%} ; MRB {cl['mrb']:.1f} ; valeur d'investissement à {TAUX_PLEX:.2%} = {fmt(cl['valeur_investissement_5_25'])} $")302    fb = faisabilite(); out["faisabilite"] = fb303    b = fb["bloc"]304    print(f"\n== D. Conversion — immeuble locatif de 8 logements : RBP {fmt(b['rbp'])} ; RBE {fmt(b['rbe'])} ; DE {fmt(b['depenses'])} ; RNE {fmt(b['noi'])} ; R {b['taux']:.2%} → V_bloc {fmt(b['valeur'])} $ ({fmt(b['valeur']/8)} $/log., MRB {b['mrb']:.1f})")305    cal = fb["calendrier"]306    print("  valeurs libres des fractions : " + ", ".join(f"{k} {fmt(v)}" for k, v in cal["valeurs"].items()) + f" ; somme {fmt(cal['somme_libre'])} $")307    print(f"  libres ou préemption à l'an 1 : {cal['n_libres_an1']} fractions ; occupées : {cal['n_occupes']} (valeur libre {fmt(cal['V_occ'])} $) ; ρ = {ROTATION:.0%}, décote occupée {DECOTE_OCCUPE:.0%}, r = {R_PROMOTEUR:.0%}")308    print("  produit brut par année : " + ", ".join(f"an {t} {fmt(p)}" for t, p in enumerate(fb["produit"]) if t > 0))309    print("  flux : " + ", ".join(f"t{t} {f:,.0f}".replace(",", " ") for t, f in enumerate(fb["flux"])))310    print(f"  coûts de conversion {fmt(fb['couts'])} ; frais de vente {fmt(fb['frais_vente_total'])} ; portage {fmt(fb['portage_total'])} ; somme nette non actualisée {fmt(fb['somme_nette_non_actualisee'])}")311    print(f"  V_conv = {fmt(fb['v_conv'])} $ ; V_bloc = {fmt(fb['v_bloc'])} $ ; prime {fmt(fb['prime'])} $ ({fb['marge_sur_bloc']:+.1%})")312    fo = faisabilite(vendre_occupe=True); out["faisabilite_occ"] = fo313    print(f"  variante : tout vendre à l'an 1 (occupées avec décote) → V_conv {fmt(fo['v_conv'])} $ ; prime {fmt(fo['prime'])} $")314    sc = sensibilite_conversion(); out["sens_conv"] = sc315    print("  sensibilité V_conv : lignes décote occupée, colonnes ρ " + "  ".join(f"{r:.0%}" for r in sc["rotations"]) + " | vente occupée an 1")316    for d, row in sc["grille"].items():317        print(f"    δ={d:.0%} " + "  ".join(f"{v:10,.0f}" for v in row).replace(",", " ") + f" | {sc['vente_occupee_an1'][d]:10,.0f}".replace(",", " "))318    sp = seuil_prix(); out["seuil_prix"] = sp319    print(f"  point mort : les valeurs libres peuvent baisser jusqu'à {sp:.1%} de leur niveau (−{1-sp:.1%}) avant que V_conv < V_bloc")320    for r in (0.08, 0.12):321        print(f"  r = {r:.0%} : V_conv {fmt(faisabilite(r=r)['v_conv'])} $")322    return out323324325if __name__ == "__main__":326    main()327
Files · 0b0a78499997700c9aa16d44b91243a1ef46c0bb
Menu

Theme