SPB Git
24.2 KB · 344 lines python
1#!/usr/bin/env python32"""Chapitre 24 — dépréciation et désuétudes du sujet du cas 1 (Gatineau, DONNÉES SYNTHÉTIQUES).34Imprime tous les chiffres cités dans le chapitre 24 :5  * âges (chronologique, effectif selon la convention du ch. 11), vie économique et vie restante ;6  * dépréciation physique : âge-vie simple (âge chronologique / âge effectif), âge-vie modifiée, ventilée par composantes7    (fiche d'état et travaux du ch. 11, traite.cout.depreciation_ventilee) ;8  * désuétude fonctionnelle : carence curable du sujet (salle d'eau), exemples incurables (perte de loyer × MRB du cas 2 ;9    perte de RNE capitalisée) ; désuétude externe (ventes appariées, capitalisation) ;10  * extraction de marché sur les 600 ventes du cas 1 : coût neuf par vente (élasticité de taille du ch. 21), prix ramenés à la11    date d'évaluation (tendance du modèle hédonique), valeur de terrain par secteur extraite des ventes récentes, dépréciation12    par vente, profil en fonction de l'âge (médianes par tranche, ajustements linéaire et quadratique) ;13  * profils comparés : âge-vie linéaire, extraction, courbe hédonique (coefficients âge et âge² de traite.cas1.hedonique)14    et profil « vrai » du générateur (data/synthetic/README.md), convertis en dépréciation du bâtiment ;15  * valeur par le coût du sujet (traite.cout.valeur_par_cout) selon deux lectures de la dépréciation, et sensibilité.1617Usage (racine du dépôt) : PYTHONPATH=analysis/python python3 analysis/python/cases/ch24_depreciation.py18Toutes les formules de dépréciation proviennent de analysis/python/traite/cout.py (aucune réécriture)."""19from __future__ import annotations20import sys, pathlib21import numpy as np, pandas as pd22sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1]))23sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))24from traite import cout as ct, cas125from traite import hedonique as h26import ch21_cout_neuf as c2127import ch11_inspection as c112829ROOT = pathlib.Path(__file__).resolve().parents[3]3031# --- conventions et hypothèses déclarées ---------------------------------------------------------------------32VIE_ECONOMIQUE = c11.VIE_ECONOMIQUE          # 60 ans (convention des ch. 11 et 24, hypothèse pédagogique)33AGE_CHRONO = 28                              # 1998 → 202634DATE_EVAL = "2026-09-01"35ELASTICITE_TAILLE = -0.10                    # ch. 21 : coût unitaire ∝ (S/S_réf)^ε36FACTEUR_QUALITE = 0.05                       # coût neuf ± 5 % par classe de qualité (hypothèse pédagogique)37AGE_MAX_RECENT = 10                          # ventes « récentes » servant à extraire la valeur du terrain38S_REF = 165.039# Carence fonctionnelle curable du sujet (constat FICTIF du ch. 24) : aucune salle d'eau au rez-de-chaussée40SALLE_EAU_AJOUT = 16000.0                    # soumission fictive : ajout a posteriori, installé (hypothèse pédagogique)41SALLE_EAU_POSTES = {"Plomberie brute et finition": 2.0, "Vanités et accessoires (2 sdb)": 0.5, "Portes intérieures et moulures": 1.0,42                    "Gypse et tirage de joints": 12.0, "Peinture intérieure": 12.0, "Planchers céramique": 3.0}   # quantités si incluse à neuf43# Exemples illustratifs (hypothèses pédagogiques, hors sujet)44TRIPLEX_PERTE_LOYER_MOIS = 75.0              # $/mois, logement sans entrée laveuse-sécheuse45RNE_PERTE_ANNUELLE = 6000.0                  # $/an46TAUX_CAP_ILLUSTRATIF = 0.045                 # milieu de la fourchette CBRE T3 2026 Montréal low-rise B (4,25-4,75 %), calibration.json SRC-E-04547PART_BATIMENT_ILLUSTRATIVE = 0.7048PAIRES_SANS, PAIRES_AVEC, PAIRES_PART_BAT = 520000.0, 490000.0, 0.60495051def charger() -> dict:52    devis, sujet, cn = c11.charger()53    d = cas1.charger()54    m = cas1.hedonique(d["ventes"])55    fiche = c11.fiche(devis, cn)56    trav = c11.travaux(devis, cn)57    return {"devis": devis, "sujet": sujet, "cn": cn, "ventes": d["ventes"], "modele": m, "fiche": fiche, "travaux": trav}585960# --------------------------------------------------------------------------------------------- âges et âge-vie6162def ages_sujet(fiche: pd.DataFrame) -> dict:63    s = fiche[fiche["longue_duree"]].iloc[0]64    pond = float((fiche["cout_neuf"] * fiche["ratio"]).sum() / fiche["cout_neuf"].sum())65    return {"chronologique": AGE_CHRONO, "effectif_structure": float(s.age_effectif), "vie_economique": VIE_ECONOMIQUE,66            "vie_restante": ct.vie_restante(VIE_ECONOMIQUE, float(s.age_effectif)),67            "ratio_pondere": pond, "age_effectif_pondere": pond * VIE_ECONOMIQUE, "indice_etat": c11.indice_etat(fiche)}686970def variantes_physique(cn: dict, fiche: pd.DataFrame, trav: pd.DataFrame) -> dict:71    """Quatre lectures de la dépréciation physique du sujet (toutes sur C_N = 397 567,33 $)."""72    a_eff = ages_sujet(fiche)["effectif_structure"]73    curable = float(trav["cout"].sum())74    simple_chrono = ct.depreciation_age_vie(cn["total"], AGE_CHRONO, VIE_ECONOMIQUE)75    simple_eff = ct.depreciation_age_vie(cn["total"], a_eff, VIE_ECONOMIQUE)76    modifiee = ct.depreciation_age_vie_modifiee(cn["total"], curable, a_eff, VIE_ECONOMIQUE)77    ventilee = c11.depreciation(fiche, trav, cn)78    return {"age_vie_chrono": simple_chrono, "age_vie_effectif": simple_eff, "modifiee": modifiee, "ventilee": ventilee,79            "curable": curable, "age_effectif": a_eff}808182# ------------------------------------------------------------------------------------------ désuétudes8384def carence_salle_eau(devis: pd.DataFrame, cn: dict) -> dict:85    """Désuétude fonctionnelle curable (carence) : coût d'ajout a posteriori − coût de l'élément s'il avait été inclus à neuf.86    Le coût « à neuf » est reconstitué à partir des prix unitaires du devis (quantités déclarées), majoré comme le coût neuf."""87    g = c11.facteur_majoration(cn)88    direct = 0.089    for poste, q in SALLE_EAU_POSTES.items():90        pu = float(devis.loc[devis["poste"] == poste, "prix_unitaire"].iloc[0])91        direct += pu * q92    cout_si_neuf = direct * g93    d = ct.desuetude_fonctionnelle_curable(SALLE_EAU_AJOUT, cout_si_neuf, 0.0)94    return {"cout_si_neuf_direct": direct, "cout_si_neuf": cout_si_neuf, "cout_ajout": SALLE_EAU_AJOUT, "desuetude": d}959697def exemples_incurables() -> dict:98    from traite import cas299    ind = cas2.indicateurs_marche(cas2.comparables_plex(cas2.charger()["ventes"], cas2.charger()["sujet"]))100    mrb_annuel = ind["mrb_median_triplex"]101    loyer = ct.desuetude_incurable_par_loyer(TRIPLEX_PERTE_LOYER_MOIS, mrb_annuel * 12)102    rne = ct.desuetude_incurable_par_rne(RNE_PERTE_ANNUELLE, TAUX_CAP_ILLUSTRATIF, PART_BATIMENT_ILLUSTRATIVE)103    rne_total = ct.desuetude_incurable_par_rne(RNE_PERTE_ANNUELLE, TAUX_CAP_ILLUSTRATIF, 1.0)104    paires = ct.desuetude_externe_ventes_appariees(PAIRES_SANS, PAIRES_AVEC, PAIRES_PART_BAT)105    return {"mrb_annuel": mrb_annuel, "mrb_mensuel": mrb_annuel * 12, "perte_loyer_capitalisee": loyer,106            "perte_rne_capitalisee_totale": rne_total, "perte_rne_batiment": rne, "paires": paires}107108109# ------------------------------------------------------------------------------------------ extraction de marché110111def cout_neuf_ventes(v: pd.DataFrame, cn_total: float, elasticite: float = ELASTICITE_TAILLE, fq: float = FACTEUR_QUALITE) -> pd.Series:112    """Coût neuf de chaque vente, transposé du sujet : C_N × (S/165)^(1+ε) × (1 + fq·(qualité − 3))."""113    unit = ct.cout_par_superficie_ajuste(cn_total / S_REF, S_REF, v["superficie_habitable_m2"].astype(float), elasticite)114    return unit * v["superficie_habitable_m2"].astype(float) * (1.0 + fq * (v["qualite_1a5"].astype(float) - 3.0))115116117def prix_a_la_date(v: pd.DataFrame, modele: dict, date_eval: str = DATE_EVAL) -> pd.Series:118    """Prix ramenés à la date d'évaluation par la tendance mensuelle du modèle hédonique (ch. 17-18)."""119    g = modele["croissance_mensuelle"]120    mois = (pd.Timestamp(date_eval) - pd.to_datetime(v["date_vente"])).dt.days / 30.4375121    return v["prix_vente"].astype(float) * (1.0 + g) ** mois122123124def valeur_terrain_par_secteur(v: pd.DataFrame, p_adj: pd.Series, cn_i: pd.Series, age_max: int = AGE_MAX_RECENT) -> pd.DataFrame:125    """Valeur du terrain par secteur, extraite des ventes récentes : V_L = P_adj − C_N (1 − âge/L) (extraction, ch. 25)."""126    r = v[v["age"] <= age_max].copy()127    r["p_adj"] = p_adj[r.index]; r["cn"] = cn_i[r.index]128    r["dep"] = [ct.depreciation_age_vie(c, a, VIE_ECONOMIQUE) for c, a in zip(r["cn"], r["age"])]129    r["vl"] = r["p_adj"] - (r["cn"] - r["dep"])130    r["part"] = r["vl"] / r["p_adj"]131    g = r.groupby("secteur").agg(n=("vl", "size"), vl_mediane=("vl", "median"), vl_moyenne=("vl", "mean"), vl_ecart_type=("vl", "std"),132                                 part_mediane=("part", "median"), terrain_m2_median=("superficie_terrain_m2", "median"))133    g["vl_par_m2"] = g["vl_mediane"] / g["terrain_m2_median"]134    return g135136137def extraction(v: pd.DataFrame, modele: dict, cn_total: float, elasticite: float = ELASTICITE_TAILLE, fq: float = FACTEUR_QUALITE,138               age_max: int = AGE_MAX_RECENT) -> tuple[pd.DataFrame, pd.DataFrame]:139    """Dépréciation totale extraite de chaque vente : D = C_N − (P_adj − V_L) (traite.cout.extraction_depreciation_marche)."""140    w = v.copy()141    w["cn"] = cout_neuf_ventes(w, cn_total, elasticite, fq)142    w["p_adj"] = prix_a_la_date(w, modele)143    vl = valeur_terrain_par_secteur(w, w["p_adj"], w["cn"], age_max)144    w["vl"] = w["secteur"].map(vl["vl_mediane"])145    ext = [ct.extraction_depreciation_marche(p, l, c, a) for p, l, c, a in zip(w["p_adj"], w["vl"], w["cn"], w["age"])]146    w["contribution_batiment"] = [e["contribution_batiment"] for e in ext]147    w["D"] = [e["depreciation"] for e in ext]148    w["taux"] = [e["taux"] for e in ext]149    w["taux_annuel"] = [e.get("taux_annuel", np.nan) for e in ext]150    return w, vl151152153def ajuster_profil(w: pd.DataFrame) -> dict:154    """Ajustements du taux de dépréciation extrait sur l'âge : linéaire (constante), quadratique, linéaire par l'origine."""155    a = w["age"].astype(float).values; y = w["taux"].values; n = len(w)156    lin = h.mco(np.column_stack([np.ones(n), a]), y)157    quad = h.mco(np.column_stack([np.ones(n), a, a ** 2]), y)158    orig = h.mco(a.reshape(-1, 1), y)159    return {"lineaire": lin, "quadratique": quad, "origine": orig,160            "taux_annuel_origine": float(orig["beta"][0]), "vie_implicite": 1.0 / float(orig["beta"][0])}161162163def taux_quadratique(quad: dict, age) -> np.ndarray:164    b = quad["beta"]; age = np.asarray(age, dtype=float)165    return b[0] + b[1] * age + b[2] * age ** 2166167168def tranches(w: pd.DataFrame, bornes=(0, 10, 20, 30, 40, 50, 60, 100)) -> pd.DataFrame:169    g = w.groupby(pd.cut(w["age"], bornes), observed=True)["taux"].agg(["size", "median", "mean", "std"])170    g.index = [f"{int(i.left) + (1 if i.left else 0)}-{int(i.right)}" for i in g.index]171    return g172173174# ------------------------------------------------------------------------------------------ profils comparés175176def prix_neuf_sujet(modele: dict, sujet: dict) -> float:177    """Prédiction hédonique du sujet s'il était neuf (âge 0), même date, correction de Duan."""178    s = dict(sujet); s["age"] = 0; s["annee_construction"] = 2026179    return float(cas1.predire(modele, cas1.sujet_df(s))[0])180181182def profil_total_hedonique(modele: dict, age) -> np.ndarray:183    """Baisse du prix TOTAL avec l'âge selon le modèle : 1 − exp(β₁ âge + β₂ âge²)."""184    b1 = float(modele["coef"].loc["age", "coef"]); b2 = float(modele["coef"].loc["age2_100", "coef"]) / 100.0185    age = np.asarray(age, dtype=float)186    return 1.0 - np.exp(b1 * age + b2 * age ** 2)187188189def profil_total_generateur(age) -> np.ndarray:190    """Loi de génération du cas 1 (data/synthetic/README.md) : −0,0055·âge + 0,00003·âge²."""191    age = np.asarray(age, dtype=float)192    return 1.0 - np.exp(-0.0055 * age + 0.00003 * age ** 2)193194195def total_vers_batiment(d_total, alpha: float) -> np.ndarray:196    """Conversion : si le terrain vaut une part α du prix neuf et ne se déprécie pas, d_B = d_total / (1 − α)."""197    return np.asarray(d_total, dtype=float) / (1.0 - alpha)198199200def profils(modele: dict, sujet: dict, vl: float, quad: dict, ages=np.arange(0, 81)) -> dict:201    p0 = prix_neuf_sujet(modele, sujet)202    alpha = vl / p0203    return {"ages": ages, "prix_neuf": p0, "alpha": alpha,204            "age_vie": np.minimum(ages / VIE_ECONOMIQUE, 1.0),205            "extraction": taux_quadratique(quad, ages),206            "hedonique_total": profil_total_hedonique(modele, ages), "generateur_total": profil_total_generateur(ages),207            "hedonique_bat": total_vers_batiment(profil_total_hedonique(modele, ages), alpha),208            "generateur_bat": total_vers_batiment(profil_total_generateur(ages), alpha)}209210211# ------------------------------------------------------------------------------------------ valeur par le coût212213def valeur_terrain_retenue(vl: pd.DataFrame, secteur: str = "Hull") -> dict:214    med = float(vl.loc[secteur, "vl_mediane"])215    return {"mediane": med, "retenue": round(med / 1000.0) * 1000.0, "n": int(vl.loc[secteur, "n"]), "part": float(vl.loc[secteur, "part_mediane"])}216217218def valeur_sujet(cn: dict, vl_retenue: float, quad: dict, fonc: float, variantes: dict) -> dict:219    """Deux lectures : (A) dépréciation physique ventilée (ch. 11) + désuétude fonctionnelle ; (B) dépréciation extraite du marché220    à l'âge chronologique + désuétude fonctionnelle spécifique au sujet."""221    t28 = float(taux_quadratique(quad, AGE_CHRONO))222    d_ext = t28 * cn["total"]223    A = ct.valeur_par_cout(vl_retenue, cn["total"], variantes["ventilee"]["total"], fonc, 0.0)224    B = ct.valeur_par_cout(vl_retenue, cn["total"], d_ext, fonc, 0.0)225    return {"taux_extrait_28": t28, "d_extraite": d_ext, "A_ventilee": A, "B_extraction": B,226            "ecart": B["valeur"] - A["valeur"], "valeur_retenue": B["valeur"], "valeur_arrondie": round(B["valeur"] / 5000.0) * 5000.0}227228229# ------------------------------------------------------------------------------------------ sensibilité230231def grille_age_vie(cn: dict, curable: float, vies=(50, 60, 70, 80, 100), ages_eff=(20, 25, 29, 33, 37)) -> pd.DataFrame:232    """Taux de dépréciation (âge-vie modifiée, curable fixé) pour chaque couple (vie économique, âge effectif)."""233    rows = {}234    for L in vies:235        rows[L] = {a: ct.depreciation_age_vie_modifiee(cn["total"], curable, a, L)["total"] / cn["total"] for a in ages_eff}236    return pd.DataFrame(rows).T237238239def sensibilite_extraction(v: pd.DataFrame, modele: dict, cn_total: float) -> pd.DataFrame:240    """Taux extrait à 28 ans et valeur du terrain (Hull) selon les choix de l'extraction."""241    rows = []242    for nom, kw in [("cas de base", {}), ("ventes récentes ≤ 5 ans", {"age_max": 5}), ("ventes récentes ≤ 15 ans", {"age_max": 15}),243                    ("élasticité −0,05", {"elasticite": -0.05}), ("élasticité −0,15", {"elasticite": -0.15}),244                    ("qualité ±0 %", {"fq": 0.0}), ("qualité ±10 %", {"fq": 0.10})]:245        w, vl = extraction(v, modele, cn_total, **kw)246        q = ajuster_profil(w)["quadratique"]247        rows.append({"variante": nom, "vl_hull": float(vl.loc["Hull", "vl_mediane"]), "taux_28": float(taux_quadratique(q, AGE_CHRONO)),248                     "taux_annuel": ajuster_profil(w)["taux_annuel_origine"]})249    return pd.DataFrame(rows)250251252def tornade(cn: dict, vl: float, d_ext: float, fonc: float, variantes: dict, sens_ext: pd.DataFrame) -> list[dict]:253    base = ct.valeur_par_cout(vl, cn["total"], d_ext, fonc)["valeur"]254    out = []255    def add(param, bas, haut, etiquette):256        out.append({"param": param, "bas": bas, "haut": haut, "base": base, "etiquette": etiquette})257    add("Valeur du terrain ±15 %", ct.valeur_par_cout(vl * 0.85, cn["total"], d_ext, fonc)["valeur"], ct.valeur_par_cout(vl * 1.15, cn["total"], d_ext, fonc)["valeur"], f"{vl*0.85:,.0f} → {vl*1.15:,.0f} $".replace(",", " "))258    t = sens_ext["taux_28"]259    add("Choix d'extraction (taux à 28 ans)", ct.valeur_par_cout(vl, cn["total"], t.max() * cn["total"], fonc)["valeur"], ct.valeur_par_cout(vl, cn["total"], t.min() * cn["total"], fonc)["valeur"], f"{t.max()*100:.1f} % → {t.min()*100:.1f} %")260    add("Coût neuf ±8,6 % (date des prix)", ct.valeur_par_cout(vl, cn["total"] / 1.086, d_ext / 1.086, fonc)["valeur"], ct.valeur_par_cout(vl, cn["total"] * 1.086, d_ext * 1.086, fonc)["valeur"], "indice ×0,921 → ×1,086")261    add("Désuétude fonctionnelle 0 → 2×", ct.valeur_par_cout(vl, cn["total"], d_ext, 2 * fonc)["valeur"], ct.valeur_par_cout(vl, cn["total"], d_ext, 0.0)["valeur"], f"{2*fonc:,.0f} → 0 $".replace(",", " "))262    add("Lecture ventilée (ch. 11) au lieu de l'extraction", ct.valeur_par_cout(vl, cn["total"], variantes["ventilee"]["total"], fonc)["valeur"], base, f"{variantes['ventilee']['total']/cn['total']*100:.1f} % → {d_ext/cn['total']*100:.1f} %")263    return out264265266# ------------------------------------------------------------------------------------------ main267268def main() -> dict:269    d = charger()270    cn, fiche, trav, v, m, sujet, devis = d["cn"], d["fiche"], d["travaux"], d["ventes"], d["modele"], d["sujet"], d["devis"]271    f2 = lambda x: f"{x:,.2f}".replace(",", " "); f0 = lambda x: f"{x:,.0f}".replace(",", " ")272    print("== DONNÉES SYNTHÉTIQUES — sujet cas 1 :", sujet["type"], sujet["annee_construction"], sujet["superficie_habitable_m2"], "m² hab.,", sujet["secteur"], "; évaluation", DATE_EVAL)273    print(f"== Coût neuf C_N (ch. 21) = {f2(cn['total'])} $")274    ag = ages_sujet(fiche)275    print(f"== Âges : chronologique {ag['chronologique']} ans ; effectif de la structure {ag['effectif_structure']:.1f} ans (convention ch. 11, λ = {c11.LAMBDA}) ; "276          f"vie économique {ag['vie_economique']:.0f} ans ; vie restante {ag['vie_restante']:.1f} ans ; indice d'état {ag['indice_etat']:.3f} ; "277          f"âge effectif pondéré par les coûts {ag['age_effectif_pondere']:.1f} ans")278    var = variantes_physique(cn, fiche, trav)279    print("== Dépréciation physique — quatre lectures :")280    print(f"   âge-vie simple, âge chronologique 28/60   : {f2(var['age_vie_chrono'])} $ ({var['age_vie_chrono']/cn['total']*100:.2f} %)")281    print(f"   âge-vie simple, âge effectif {var['age_effectif']:.0f}/60      : {f2(var['age_vie_effectif'])} $ ({var['age_vie_effectif']/cn['total']*100:.2f} %)")282    mo = var["modifiee"]283    print(f"   âge-vie modifiée : curable {f2(mo['curable'])} + incurable {f2(mo['incurable'])} = {f2(mo['total'])} $ ({mo['total']/cn['total']*100:.2f} %)")284    ve = var["ventilee"]285    print(f"   ventilée (ch. 11) : curable {f2(ve['curable'])} + courte durée {f2(sum(ve['courte_duree'].values()))} + longue durée {f2(ve['longue_duree'])} "286          f"(base {f2(ve['base_longue'])} × {ve['age_effectif_structure']:.0f}/60) = {f2(ve['total'])} $ ({ve['taux']*100:.2f} %)")287    courtes = fiche[(~fiche["longue_duree"]) & (fiche["composante"] != "Entrée asphaltée")]288    print(f"   composantes de courte durée : {len(courtes)} ; coût neuf {f2(courtes['cout_neuf'].sum())} $ ; dépréciation {f2(sum(ve['courte_duree'].values()))} $ "289          f"({sum(ve['courte_duree'].values())/courtes['cout_neuf'].sum()*100:.1f} % de leur coût neuf)")290    fin = [(k, x, fiche.loc[fiche['composante'] == k, 'cout_neuf'].iloc[0]) for k, x in ve["courte_duree"].items()]291    for k, x, c in sorted(fin, key=lambda t: -t[1]):292        print(f"     {k:58s} coût neuf {f2(c):>11s} $  dépréciation {f2(x):>11s} $  ({x/c*100:5.1f} %)")293    se = carence_salle_eau(devis, cn)294    print("== Désuétude fonctionnelle curable du sujet (carence : salle d'eau au rez-de-chaussée, constat fictif) :")295    print(f"   coût si incluse à neuf : direct {f2(se['cout_si_neuf_direct'])} $ → majoré {f2(se['cout_si_neuf'])} $ ; ajout a posteriori {f2(se['cout_ajout'])} $ ; désuétude = {f2(se['desuetude'])} $")296    ex = exemples_incurables()297    print("== Exemples incurables (hypothèses pédagogiques) :")298    print(f"   triplex cas 2 : perte {TRIPLEX_PERTE_LOYER_MOIS:.0f} $/mois × MRB mensuel {ex['mrb_mensuel']:.2f} (= 12 × {ex['mrb_annuel']:.2f}) = {f2(ex['perte_loyer_capitalisee'])} $")299    print(f"   perte de RNE {f0(RNE_PERTE_ANNUELLE)} $/an / {TAUX_CAP_ILLUSTRATIF*100:.2f} % = {f2(ex['perte_rne_capitalisee_totale'])} $ ; part bâtiment {PART_BATIMENT_ILLUSTRATIVE:.0%} → {f2(ex['perte_rne_batiment'])} $")300    print(f"   paires : ({f0(PAIRES_SANS)} − {f0(PAIRES_AVEC)}) × {PAIRES_PART_BAT:.0%} = {f2(ex['paires'])} $")301    w, vl = extraction(v, m, cn["total"])302    print(f"== Extraction de marché sur {len(w)} ventes : tendance {m['croissance_mensuelle']*100:.3f} %/mois ; C_N par vente = C_N × (S/165)^(1{ELASTICITE_TAILLE:+.2f}) × (1 ± {FACTEUR_QUALITE:.0%}/classe)")303    print(f"   Valeur du terrain par secteur (ventes ≤ {AGE_MAX_RECENT} ans) :")304    print(vl.round(3).to_string())305    vlr = valeur_terrain_retenue(vl)306    print(f"   → Hull : médiane {f2(vlr['mediane'])} $ sur {vlr['n']} ventes (part médiane du prix {vlr['part']*100:.1f} %) ; VALEUR DE TERRAIN RETENUE V_L = {f0(vlr['retenue'])} $ ({vlr['retenue']/620:.0f} $/m² pour 620 m²)")307    tr = tranches(w)308    print("   Taux de dépréciation extrait par tranche d'âge :"); print(tr.round(3).to_string())309    aj = ajuster_profil(w)310    q = aj["quadratique"]; l = aj["lineaire"]311    print(f"   linéaire : taux = {l['beta'][0]:.4f} + {l['beta'][1]:.5f}·âge (se {l['se_hc1'][1]:.5f}), R² {l['r2']:.3f}")312    print(f"   quadratique : taux = {q['beta'][0]:.4f} + {q['beta'][1]:.5f}·âge {q['beta'][2]:+.7f}·âge² (se {q['se_hc1'][0]:.4f}, {q['se_hc1'][1]:.5f}, {q['se_hc1'][2]:.7f}), R² {q['r2']:.3f}")313    print(f"   par l'origine : {aj['taux_annuel_origine']*100:.3f} % de C_N par année → vie économique linéaire implicite {aj['vie_implicite']:.0f} ans")314    for a in (10, 28, 40, 60):315        print(f"   taux quadratique à {a:2d} ans : {float(taux_quadratique(q, a))*100:.2f} %")316    pr = profils(m, sujet, vlr["retenue"], q)317    print(f"== Profils : prix hédonique du sujet neuf {f0(pr['prix_neuf'])} $ ; part du terrain α = {pr['alpha']:.3f}")318    print("   âge | âge-vie | extraction | hédonique total → bâtiment | générateur total → bâtiment")319    for a in (10, 20, 28, 40, 60, 80):320        i = int(a)321        print(f"   {a:3d} | {pr['age_vie'][i]*100:6.1f} | {pr['extraction'][i]*100:7.1f} | {pr['hedonique_total'][i]*100:6.1f} → {pr['hedonique_bat'][i]*100:6.1f} | {pr['generateur_total'][i]*100:6.1f} → {pr['generateur_bat'][i]*100:6.1f}")322    vs = valeur_sujet(cn, vlr["retenue"], q, se["desuetude"], var)323    print("== Valeur par le coût du sujet (traite.cout.valeur_par_cout) :")324    A, B = vs["A_ventilee"], vs["B_extraction"]325    print(f"   (A) ventilée : V_L {f0(vlr['retenue'])} + C_N {f2(cn['total'])} − D_phys {f2(ve['total'])} − D_fonc {f2(se['desuetude'])} = {f2(A['valeur'])} $ (dépréciation totale {A['taux_depreciation']*100:.2f} %)")326    print(f"   (B) extraction : D_extraite(28) = {vs['taux_extrait_28']*100:.2f} % × C_N = {f2(vs['d_extraite'])} $ ; V = {f0(vlr['retenue'])} + {f2(cn['total'])} − {f2(vs['d_extraite'])} − {f2(se['desuetude'])} = {f2(B['valeur'])} $ "327          f"(dépréciation totale {f2(B['depreciation_totale'])} $ = {B['taux_depreciation']*100:.2f} % ; contribution du bâtiment {f2(B['contribution_batiment'])} $)")328    print(f"   écart B − A = {f2(vs['ecart'])} $ ; VALEUR RETENUE PAR LE COÛT {f2(vs['valeur_retenue'])} $, arrondie {f0(vs['valeur_arrondie'])} $")329    gr = grille_age_vie(cn, var["curable"])330    print("== Sensibilité âge-vie modifiée (taux en %) : vie économique (lignes) × âge effectif (colonnes)"); print((gr * 100).round(1).to_string())331    sx = sensibilite_extraction(v, m, cn["total"])332    print("== Sensibilité de l'extraction :"); print(sx.assign(taux_28=sx.taux_28 * 100, taux_annuel=sx.taux_annuel * 100).round(2).to_string())333    to = tornade(cn, vlr["retenue"], vs["d_extraite"], se["desuetude"], var, sx)334    print("== Tornade (valeur par le coût) :")335    for o in to:336        print(f"   {o['param']:48s} {o['etiquette']:28s} bas {f0(o['bas'])}  haut {f0(o['haut'])}  (base {f0(o['base'])}) ; étendue {(o['haut']-o['bas'])/o['base']*100:.1f} %")337    return {"cn": cn, "fiche": fiche, "travaux": trav, "ventes": v, "modele": m, "sujet": sujet, "devis": devis, "ages": ag, "variantes": var,338            "salle_eau": se, "exemples": ex, "extraction": w, "vl": vl, "vl_retenue": vlr, "tranches": tr, "ajustements": aj, "profils": pr,339            "valeur": vs, "grille": gr, "sens_extraction": sx, "tornade": to}340341342if __name__ == "__main__":343    main()344
Files · 52713f1726a682cc02b48561b6b82a2be2ec84ba
Menu

Theme