spb/traite-evaluation-qc
Public
1#!/usr/bin/env python32"""Chapitre 45 — Cas 1 : maison unifamiliale à Gatineau (DONNÉES SYNTHÉTIQUES, propriété fictive) — dossier complet.34Ce script N'INVENTE AUCUN CHIFFRE : il importe les fonctions des chapitres de méthode et de la bibliothèque partagée5 * sélection des comparables : traite.cas1.selection (ch. 16) ;6 * grille d'ajustements et valeur pondérée : traite.cas1.ajustements_hedoniques + ch17_ajustements.reconcilier (ch. 17) ;7 * modèle hédonique, prédiction, intervalles : traite.cas1.hedonique / predire / intervalle_prediction, ch18_hedonique.prediction_sujet (ch. 18) ;8 * mesurage : ch12_mesurage.superficies (ch. 12) ; inspection : ch11_inspection (ch. 11) ;9 * coût neuf : ch21_cout_neuf (ch. 21) ; dépréciation et valeur par le coût : ch24_depreciation.main (ch. 24) ;10 * terrain : ch25_terrain (comparaison directe + extraction du ch. 24) (ch. 25) ;11 * rôle, taxes, mutations : ch09_ratios (ch. 9) ;12 * réconciliation : ch20_reconciliation.tout (ch. 20),13et n'y ajoute que ce qui est propre au dossier : le financement de l'acheteur (traite.finance), les droits de mutation à l'achat,14le sommaire du rapport et les exercices. Toute divergence entre un chiffre d'ici et un chiffre des chapitres de méthode est une15erreur à signaler : les tests (test_ch45.py) la détecteraient.1617Usage (racine du dépôt) : PYTHONPATH=analysis/python python3 analysis/python/cases/ch45_dossier.py"""18from __future__ import annotations19import sys, io, math, pathlib, contextlib20import numpy as np, pandas as pd21HERE = pathlib.Path(__file__).resolve()22sys.path.insert(0, str(HERE.parents[1])); sys.path.insert(0, str(HERE.parent))23from traite import cas1, finance as fin, cout as ct, comparables as cp24import ch16_selection as c1625import ch17_ajustements as c1726import ch18_hedonique as c1827import ch20_reconciliation as c2028import ch21_cout_neuf as c2129import ch24_depreciation as c2430import ch25_terrain as c2531import ch12_mesurage as c1232import ch11_inspection as c1133import ch09_ratios as c093435ROOT = HERE.parents[3]36GRAINE = 2026103137DATE_EVAL = "2026-09-01"3839# ----------------------------------------------------------------------------- paramètres du financement (sources : notes-C, registre ch45)40TAUX_ASSURE = 0.0412 # BdC, fonds avancés, prêts assurés à taux fixe ≥ 5 ans, août 2026 (bdctauxhypothecaires2026)41TAUX_NON_ASSURE = 0.0458 # idem, prêts non assurés42AMORTISSEMENT_MOIS = 300 # 25 ans (SCHL : maximum 25 ans ; 30 ans réservé aux premiers acheteurs / constructions neuves)43TERME_ANS = 544MQR_PLANCHER, MQR_COUSSIN = 0.0525, 0.02 # taux admissible minimal : max(taux + 2 %, 5,25 %) (SCHL Achat ; Finances Canada 2023-12-12 ; BSIF pour le non assuré)45ABD_MAX, ATD_MAX = 0.39, 0.44 # SCHL : « restricts debt service ratios to 39 % (GDS) and 44 % (TDS) »46PRIMES_SCHL = [(0.65, 0.0060), (0.75, 0.0170), (0.80, 0.0240), (0.85, 0.0280), (0.90, 0.0310), (0.95, 0.0400)] # SCHL Achat, propriétaires-occupants47PLAFOND_ASSURABLE = 1_500_000.0 # prix maximal admissible (SCHL ; Finances Canada 2024-12-15)48TAXE_PRIME_QC = 0.09 # taxe québécoise sur les primes d'assurance : NON VÉRIFIÉE sur une source primaire (Revenu Québec inaccessible le 2026-10-31) ;49 # la SCHL indique seulement que la prime est « subject to provincial sales tax » au Québec. Hypothèse déclarée.50CHAUFFAGE_ANNUEL = 2400.0 # hypothèse pédagogique déclarée (coût de chauffage retenu par le prêteur dans l'ABD) — aucune source51AUTRES_DETTES_MENSUELLES = 500.0 # hypothèse pédagogique déclarée (prêt auto, cartes) pour l'ATD52SCENARIOS_MISE = {"minimale": None, "10 %": 0.10, "20 %": 0.20} # None → mise de fonds minimale réglementaire535455def mise_de_fonds_minimale(prix: float) -> float:56 """SCHL / Finances Canada : 5 % de la première tranche de 500 000 $ + 10 % du reste (prix < 1,5 M$)."""57 if prix >= PLAFOND_ASSURABLE:58 raise ValueError("prix non assurable (≥ 1,5 M$) : mise de fonds d'au moins 20 %")59 return 0.05 * min(prix, 500_000.0) + 0.10 * max(prix - 500_000.0, 0.0)606162def taux_prime(rpv: float) -> float:63 """Prime SCHL (propriétaire-occupant) selon le rapport prêt-valeur ; 0 si RPV ≤ 80 % (prêt conventionnel non assuré)."""64 if rpv <= 0.80 + 1e-12:65 return 0.066 for borne, t in PRIMES_SCHL:67 if rpv <= borne + 1e-12:68 return t69 raise ValueError("RPV > 95 % : non assurable")707172# ----------------------------------------------------------------------------- 1. héritages des chapitres de méthode73def _silencieux(f, *a, **k):74 with contextlib.redirect_stdout(io.StringIO()):75 return f(*a, **k)767778def heritage() -> dict:79 """Tous les chiffres hérités, obtenus des scripts des chapitres (aucun recalcul « à la main »)."""80 t = cas1.tout() # sélection ch. 16, modèle ch. 18, grille ch. 17, prédiction, intervalle81 sel = t["selection"]; m = t["modele"]; s = t["sujet"]82 b17 = c17.base(); rec17 = c17.reconcilier(b17) # ch. 17 : poids inverse du brut, valeur pondérée83 pred18 = c18.prediction_sujet(m, s) # ch. 18 : prédiction, Duan, intervalles 95 et 80 %, prédiction au 2024-07-0184 r20 = c20.tout() # ch. 20 : bootstrap, coût (ch. 24 importé), combinaison, vérité85 ic95 = (float(r20["exercices"]["ex2"]["ic_bas"]), float(r20["exercices"]["ex2"]["ic_haut"])) # IC 95 % de la valeur (ch. 20, eq. 20.6)86 r24 = r20["cout"]["r24"] # ch. 24 complet (main() déjà exécuté silencieusement par le ch. 20)87 devis, sujet, cn = c11.charger() # ch. 21 (coût neuf) et ch. 11 (fiche)88 fiche = c11.fiche(devis, cn); trav = c11.travaux(devis, cn); dv = c11.depreciation(fiche, trav, cn)89 sup12 = c12.superficies() # ch. 1290 devis21, _ = c21.charger(); g21 = c21.regrouper(devis21); sup21 = c21.superficies(sujet, devis21)91 d09 = c09.charger(); et09 = c09.etude(d09); ds09 = c09.dossier_sujet(d09, et09) # ch. 992 comp25 = _silencieux(c25.comparaison) # ch. 25 : comparaison directe des terrains93 extr25 = _silencieux(c25.extraction_recente_ch24, t)94 cand = sel["candidats"]95 return {"t": t, "sujet": s, "ventes": t["ventes"], "modele": m, "selection": sel, "candidats": cand, "comparables": sel["comparables"],96 "etapes16": sel["etapes"], "adj17": b17["adj"], "rec17": rec17, "V_comp": rec17["inverse_brut"]["valeur_ponderee"],97 "poids17": rec17["poids_inverse_brut"], "pred18": pred18, "V_hed": t["prediction_hedonique"], "ip95": pred18["ic95"], "ip80": pred18["ic80"],98 "ic95_valeur": ic95, "r20": r20, "r24": r24, "cn": cn, "devis": devis, "fiche": fiche, "travaux": trav, "dv11": dv, "sup12": sup12,99 "groupes21": g21, "sup21": sup21, "d09": d09, "et09": et09, "ds09": ds09, "comp25": comp25, "extr25": extr25,100 "indication_brute16": float(sel["comparables"]["prix_m2"].median() * s["superficie_habitable_m2"])}101102103# ----------------------------------------------------------------------------- 2. financement de l'acheteur (propre au ch. 45)104def financement(prix: float, mise: float | None = None, amortissement_mois: int = AMORTISSEMENT_MOIS, taxes_annuelles: float = 0.0,105 droits_mutation: float = 0.0, chauffage: float = CHAUFFAGE_ANNUEL, autres_dettes_mensuelles: float = AUTRES_DETTES_MENSUELLES) -> dict:106 """Prêt assuré ou conventionnel selon la mise de fonds ; prime SCHL (ajoutée au prêt), taxe québécoise sur la prime (payée comptant,107 hypothèse), paiement mensuel (composition semestrielle), taux admissible minimal, revenu brut minimal selon l'ABD et l'ATD,108 liquidités à la signature (mise de fonds + droits de mutation + taxe sur la prime ; frais de notaire et d'inspection exclus)."""109 mise = mise_de_fonds_minimale(prix) if mise is None else mise110 pret_base = prix - mise; rpv = pret_base / prix111 assure = rpv > 0.80112 tp = taux_prime(rpv); prime = pret_base * tp; taxe = prime * TAXE_PRIME_QC113 pret = pret_base + prime114 taux = TAUX_ASSURE if assure else TAUX_NON_ASSURE115 h = fin.Hypotheque(pret, taux, amortissement_mois)116 mqr = fin.taux_admissible_minimal(taux, MQR_PLANCHER, MQR_COUSSIN)117 hq = fin.Hypotheque(pret, mqr, amortissement_mois)118 pith_q = hq.service_dette_annuel + taxes_annuelles + chauffage119 revenu_abd = pith_q / ABD_MAX120 revenu_atd = (pith_q + 12 * autres_dettes_mensuelles) / ATD_MAX121 return {"prix": prix, "mise": mise, "mise_pct": mise / prix, "pret_base": pret_base, "rpv": rpv, "assure": assure, "taux_prime": tp,122 "prime": prime, "taxe_prime": taxe, "pret": pret, "rpv_avec_prime": pret / prix, "taux": taux, "amortissement_mois": amortissement_mois,123 "hypotheque": h, "paiement": h.paiement, "service_dette": h.service_dette_annuel, "constante": h.constante_hypothecaire,124 "interets_an1": h.interets_annee(1), "capital_an1": h.capital_annee(1), "solde_terme": h.solde(TERME_ANS * 12),125 "mqr": mqr, "paiement_mqr": hq.paiement, "pith_qualification": pith_q, "revenu_min_abd": revenu_abd, "revenu_min_atd": revenu_atd,126 "revenu_min": max(revenu_abd, revenu_atd), "contrainte_revenu": "ATD" if revenu_atd > revenu_abd else "ABD",127 "taxes_annuelles": taxes_annuelles, "chauffage": chauffage, "autres_dettes": autres_dettes_mensuelles,128 "droits_mutation": droits_mutation, "liquidites": mise + droits_mutation + taxe}129130131def renouvellement(f: dict, hausses_pb=(0, 100, 200, 300), taxes_annuelles: float = 0.0) -> pd.DataFrame:132 """Au terme de 5 ans, le solde est refinancé au taux initial + hausse sur l'amortissement résiduel."""133 solde = f["solde_terme"]; n = f["amortissement_mois"] - TERME_ANS * 12; rows = []134 for pb in hausses_pb:135 taux = f["taux"] + pb / 10_000136 h = fin.Hypotheque(solde, taux, n)137 pith = h.service_dette_annuel + taxes_annuelles + f["chauffage"]138 rows.append({"hausse_pb": pb, "taux": taux, "solde": solde, "paiement": h.paiement, "service_dette": h.service_dette_annuel,139 "hausse_paiement_pct": h.paiement / f["paiement"] - 1, "revenu_min_abd": pith / ABD_MAX})140 return pd.DataFrame(rows)141142143def valeur_d_emprunt(prix: float, valeur_preteur: float, mise_disponible: float) -> dict:144 """Le prêteur prête sur la moindre du prix et de la valeur d'emprunt (RPV maximal 95 %) ; la mise de fonds doit combler l'écart."""145 base = min(prix, valeur_preteur)146 pret_max = 0.95 * base147 mise_requise = max(prix - pret_max, mise_de_fonds_minimale(prix))148 return {"valeur_preteur": valeur_preteur, "base": base, "pret_max": pret_max, "mise_requise": mise_requise,149 "mise_supplementaire": mise_requise - mise_disponible}150151152# ----------------------------------------------------------------------------- 3. dossier153_CACHE: dict = {}154155def dossier() -> dict:156 if "D" in _CACHE:157 return _CACHE["D"]158 H = heritage()159 cb = H["r20"]["combinaison"]; co = H["r20"]["cout"]; bs = H["r20"]["bootstrap"]160 conclusion = float(cb["conclusion"]); fourchette = tuple(float(x) for x in cb["fourchette_conclue"])161 prix = conclusion # prix supposé = valeur la plus probable (mandat de financement)162 role = float(H["sujet"]["valeur_role_2025"])163 taxes = H["ds09"]["taxes_role"] # compte de taxes 2026 au rôle fictif (ch. 9)164 # droits de mutation à l'achat (Gatineau 2026, barème à compter du 25 février 2026) : base = max(prix ; rôle × facteur réel 1,02)165 base_mut = max(prix, role * c09.FACTEUR_GATINEAU_2026)166 droits = c09.droits_mutation(base_mut, c09.BAREME_MUTATION_GAT)167 droits_general = c09.droits_mutation(base_mut, c09.BAREME_MUTATION_QC)168 F = {k: financement(prix, None if v is None else v * prix, taxes_annuelles=taxes["total"], droits_mutation=droits) for k, v in SCENARIOS_MISE.items()}169 F["minimale 30 ans"] = financement(prix, None, 360, taxes_annuelles=taxes["total"], droits_mutation=droits)170 ren = renouvellement(F["minimale"], taxes_annuelles=taxes["total"])171 ve = {v: valeur_d_emprunt(prix, v, F["minimale"]["mise"]) for v in (fourchette[0], conclusion, fourchette[1])}172 # sommaire : indications, poids, écarts-types (ch. 20)173 tab20 = cb["tableau"]; raisonnee = tab20[tab20["schema"] == "raisonnée (retenue)"].iloc[0]174 D = {**H, "conclusion": conclusion, "fourchette": fourchette, "V_raisonnee": float(cb["V_raisonnee"]), "sd_conclusion": float(raisonnee["ecart_type"]),175 "poids20": {"comparaison": 0.40, "hedonique": 0.45, "cout": 0.15}, "indications": {"comparaison": float(cb["V"][0]), "hedonique": float(cb["V"][1]), "cout": float(cb["V"][2])},176 "ecarts_types20": {"comparaison": float(cb["s"][0]), "hedonique": float(cb["s"][1]), "cout": float(cb["s"][2])}, "R20": cb["R"],177 "bande90": tuple(float(x) for x in cb["fourchette90"]), "q20": cb["q"], "verite": H["r20"]["verite"], "tab20": tab20,178 "V_cout": float(co["valeur"]), "V_cout_arrondie": float(co["arrondie"]), "V_L": float(co["V_L"]), "CN": float(co["CN"]),179 "D_ext": float(co["D_ext"]), "taux_ext": float(co["taux_ext"]), "D_fonc": float(co["D_fonc"]), "D_tot": float(co["D_tot"]), "taux_tot": float(co["taux_tot"]),180 "V_ventilee": float(co["ventilee"]), "taux_ventilee": float(co["taux_ventilee"]), "contribution_batiment": float(co["contribution_batiment"]),181 "prix": prix, "role": role, "taxes": taxes, "base_mutation": base_mut, "droits_mutation": droits, "droits_mutation_general": droits_general,182 "financement": F, "renouvellement": ren, "valeur_emprunt": ve, "bootstrap": bs,183 "residu_moyen_retenus": float(np.mean(np.exp(H["r20"]["base"]["adj5"]["residu_log"]) - 1)),184 "residu_moyen_autres": float(np.mean(np.exp(H["r20"]["base"]["adj16"].loc[~H["r20"]["base"]["adj16"]["retenu"], "residu_log"]) - 1)),185 "V_16": float(H["r20"]["elargissement"]["valeur_ponderee"].iloc[-1]),186 "valeur_reelle_2024": float(H["ds09"]["valeur_reelle"]), "ratio_sujet_role": float(H["ds09"]["ratio_sujet"]), "PM_fictif": float(H["et09"]["PM"]),187 "F_fictif": float(H["et09"]["F"]), "valeur_a_inscrire": float(H["ds09"]["valeur_a_inscrire"]), "hed_2024": float(H["ds09"]["hedonique"])}188 D["ratio_prix_role"] = prix / role189 D["ratio_conclusion_brute16"] = conclusion / H["indication_brute16"] - 1190 _CACHE["D"] = D191 return D192193194# ----------------------------------------------------------------------------- 4. exercices (chiffres des solutions)195def exercices(D: dict | None = None) -> dict:196 D = D or dossier(); ex = {}197 prix = D["prix"]; S = D["sujet"]["superficie_habitable_m2"]; sup = D["sup12"]198 # ex 1 : unités de comparaison de la valeur conclue199 ex["ex1"] = {"par_m2_hab": prix / S, "par_m2_brut": prix / sup["aire_etages_m2"], "par_pi2_hab": prix / (S * 10.7639),200 "par_m2_hab_plus_ss": prix / sup["habitable_sous_sol_m2"], "med_comparables_m2": float(D["comparables"]["prix_m2"].median()),201 "indication_brute": D["indication_brute16"], "ecart_pct": D["ratio_conclusion_brute16"]}202 # ex 2 : droits de mutation et liquidités au haut et au bas de la fourchette203 out = {}204 for p in (D["fourchette"][0], D["fourchette"][1]):205 base = max(p, D["role"] * c09.FACTEUR_GATINEAU_2026); dm = c09.droits_mutation(base, c09.BAREME_MUTATION_GAT)206 f = financement(p, None, taxes_annuelles=D["taxes"]["total"], droits_mutation=dm)207 out[p] = {"base": base, "droits": dm, "droits_general": c09.droits_mutation(base, c09.BAREME_MUTATION_QC), "mise": f["mise"], "prime": f["prime"], "taxe_prime": f["taxe_prime"], "liquidites": f["liquidites"]}208 ex["ex2"] = out209 # ex 3 : mise de fonds 10 % et 20 % ; coût d'opportunité au rendement des obligations 5 ans (3,58 %) ; patrimoine net à 60 mois210 R_OBLIG = 0.0358; f0 = D["financement"]["minimale"]; out = {}211 for k in ("minimale", "10 %", "20 %"):212 f = D["financement"][k]; sup = f["mise"] - f0["mise"]213 econ_mens = f0["paiement"] - f["paiement"]214 placement_5ans = sup * (1 + R_OBLIG) ** 5 # mise supplémentaire placée 5 ans (composition annuelle)215 # patrimoine net à 60 mois : valeur (constante) − solde + économies de paiement cumulées (sans intérêt) ; variante avec placement de la mise non versée216 patrimoine = prix - f["solde_terme"] + 60 * econ_mens217 out[k] = {kk: f[kk] for kk in ("mise", "pret_base", "rpv", "taux_prime", "prime", "taxe_prime", "pret", "taux", "paiement", "mqr", "paiement_mqr", "revenu_min_abd", "liquidites", "solde_terme")}218 out[k].update({"mise_supplementaire": sup, "economie_paiement_mensuel": econ_mens, "economie_paiement_5ans": 60 * econ_mens, "economie_prime": f0["prime"] - f["prime"],219 "cout_opportunite_annuel": sup * R_OBLIG, "placement_5ans": placement_5ans, "interets_placement_5ans": placement_5ans - sup,220 "patrimoine_net_60": patrimoine, "patrimoine_net_60_vs_min": patrimoine - (prix - f0["solde_terme"])})221 ex["ex3"] = out222 # ex 4 : valeur d'emprunt du prêteur inférieure au prix → montage complet au prêt maximal assuré (prime sur le prêt de base)223 out = {}224 for v, ve in D["valeur_emprunt"].items():225 pret = ve["pret_max"] if ve["mise_supplementaire"] > 0 else D["financement"]["minimale"]["pret_base"]226 mise = prix - pret; rpv = pret / prix; tp = taux_prime(rpv); prime = pret * tp; total = pret + prime227 h = fin.Hypotheque(total, TAUX_ASSURE, AMORTISSEMENT_MOIS); hq = fin.Hypotheque(total, fin.taux_admissible_minimal(TAUX_ASSURE, MQR_PLANCHER, MQR_COUSSIN), AMORTISSEMENT_MOIS)228 out[v] = {**ve, "pret": pret, "mise": mise, "rpv": rpv, "taux_prime": tp, "prime": prime, "pret_total": total, "paiement": h.paiement, "paiement_mqr": hq.paiement,229 "revenu_min_abd": (hq.service_dette_annuel + D["taxes"]["total"] + CHAUFFAGE_ANNUEL) / ABD_MAX, "liquidites": mise + D["droits_mutation"] + prime * TAXE_PRIME_QC}230 ex["ex4"] = out231 # ex 5 : renouvellement à +200 pb ; variante avec taxes +3 %/an, chauffage +2 %/an, revenu +2,5 %/an depuis le minimum initial232 r = D["renouvellement"]; ex["ex5"] = {int(k): dict(v) for k, v in r.set_index("hausse_pb").iterrows()}233 f0 = D["financement"]["minimale"]; row = ex["ex5"][200]234 taxes5 = D["taxes"]["total"] * 1.03 ** 5; chauff5 = CHAUFFAGE_ANNUEL * 1.02 ** 5; revenu5 = f0["revenu_min_abd"] * 1.025 ** 5235 pith5 = row["service_dette"] + taxes5 + chauff5236 ex["ex5"]["variante"] = {"taxes5": taxes5, "chauffage5": chauff5, "revenu5": revenu5, "pith5": pith5, "abd5": pith5 / revenu5, "revenu_min5": pith5 / ABD_MAX,237 "abd_initial_contrat": (f0["service_dette"] + D["taxes"]["total"] + CHAUFFAGE_ANNUEL) / f0["revenu_min_abd"]}238 # ex 6 : terrain aux bornes de la fourchette du ch. 25 → coût → réconciliation (poids fixes)239 out = {}240 for vl in (240_000.0, 277_000.0, 285_000.0):241 vc = ct.valeur_par_cout(vl, D["CN"], D["D_ext"], D["D_fonc"])["valeur"]242 comb = 0.40 * D["indications"]["comparaison"] + 0.45 * D["indications"]["hedonique"] + 0.15 * vc243 out[vl] = {"V_cout": vc, "combinaison": comb, "arrondie": round(comb / 5000) * 5000,244 "fourchette": (math.floor(min(D["indications"]["comparaison"], D["indications"]["hedonique"], vc) / 5000) * 5000,245 math.ceil(max(D["indications"]["comparaison"], D["indications"]["hedonique"], vc) / 5000) * 5000)}246 ex["ex6"] = out247 # ex 7 : hypothèse extraordinaire (drain français) : incidence 9 000 $, p = 0,8 ; effet sur le financement248 K = 9000.0; VH = D["conclusion"]; VnH = VH - K249 f_nH = financement(VnH, None, taxes_annuelles=D["taxes"]["total"], droits_mutation=D["droits_mutation"])250 ex["ex7"] = {"V_H": VH, "V_nonH": VnH, "delta": K, "delta_pct": K / VH, "esperance_p80": VnH + 0.8 * K, "esperance_p50": VnH + 0.5 * K,251 "mise_nonH": f_nH["mise"], "pret_nonH": f_nH["pret"], "paiement_nonH": f_nH["paiement"], "paiement_H": D["financement"]["minimale"]["paiement"]}252 return ex253254255# ----------------------------------------------------------------------------- 5. impression256def _d(x): return f"{x:,.0f} $".replace(",", " ")257def _d2(x): return f"{x:,.2f} $".replace(",", " ")258259def main() -> dict:260 D = dossier(); s = D["sujet"]261 print("=== DONNÉES SYNTHÉTIQUES — Cas 1 : cottage 1998, 165 m², Hull (Gatineau) ; évaluation au", DATE_EVAL, "===")262 print("--- Mesurage (ch. 12) :", {k: round(v, 2) for k, v in D["sup12"].items() if k in ("aire_sol_m2", "aire_etages_m2", "habitable_m2", "nette_m2", "habitable_sous_sol_m2", "garage_m2", "lot_m2")})263 print("--- Coût neuf (ch. 21) : C_N", _d2(D["cn"]["total"]), "; D", _d2(D["cn"]["direct"]), "; I", _d2(D["cn"]["indirects"]), "; E", _d2(D["cn"]["incitatif"]))264 dv = D["dv11"]265 print(f"--- Inspection (ch. 11) : indice d'état {c11.indice_etat(D['fiche']):.3f} ; âge effectif structure {dv['age_effectif_structure']:.0f} ans ; curable {_d2(dv['curable'])} ; ventilée {_d2(dv['total'])} ({dv['taux']*100:.2f} %)")266 print(f"--- Rôle et taxes (ch. 9) : rôle fictif {_d(D['role'])} (réf. 2024-07-01) ; valeur réelle 2024-07-01 {_d(D['valeur_reelle_2024'])} (hédonique {_d(D['hed_2024'])}) ; ratio {D['ratio_sujet_role']:.4f} vs PM {D['PM_fictif']:.4f} ; "267 f"valeur à inscrire {_d(D['valeur_a_inscrire'])} ; compte 2026 {_d2(D['taxes']['total'])} (municipal {_d2(D['taxes']['municipal'])}, scolaire {_d2(D['taxes']['scolaire'])}, compensation {_d2(D['taxes']['compensations'])})")268 print("--- Sélection (ch. 16) :", D["etapes16"], "; comparables", list(D["comparables"]["id_vente"]), "; indication brute", _d(D["indication_brute16"]))269 print("--- Grille (ch. 17) :\n", D["adj17"][["id_vente", "prix_vente", "conditions_marche", "superficie", "terrain", "age", "qualite", "garage", "sous_sol", "prix_ajuste", "ajust_brut_pct", "ajust_net_pct", "residu_pct"]].round(3).to_string(index=False))270 print(" poids", np.round(D["poids17"], 3), "; valeur pondérée", _d(D["V_comp"]), "; moyenne", _d(D["rec17"]["egal"]["valeur_ponderee"]), "; médiane", _d(D["rec17"]["egal"]["mediane"]))271 p = D["pred18"]272 print(f"--- Hédonique (ch. 18) : point {_d(p['naif'])} ; Duan {p['facteur_duan']:.5f} → {_d(D['V_hed'])} ; IP 95 % [{_d(D['ip95'][0])} ; {_d(D['ip95'][1])}] ; IP 80 % [{_d(D['ip80'][0])} ; {_d(D['ip80'][1])}] ; IC 95 % de la valeur [{_d(D['ic95_valeur'][0])} ; {_d(D['ic95_valeur'][1])}]")273 print(f"--- Coût (ch. 24/25) : V_L {_d(D['V_L'])} ; D extraite {_d2(D['D_ext'])} ({D['taux_ext']*100:.2f} %) ; D fonc {_d2(D['D_fonc'])} ; D totale {_d2(D['D_tot'])} ({D['taux_tot']*100:.2f} %) ; contribution bâtiment {_d2(D['contribution_batiment'])} ; V {_d2(D['V_cout'])} → {_d(D['V_cout_arrondie'])} ; ventilée {_d2(D['V_ventilee'])} ({D['taux_ventilee']*100:.2f} %)")274 c25r = D["comp25"]["reconciliation"]275 print(f" terrain ch. 25 : comparaison directe {_d(c25r['valeur_ponderee'])} ; hédonique terrains {_d(D['comp25']['prediction_hedonique'])} ; extraction ch. 24 médiane {_d(D['extr25']['mediane'])} ({D['extr25']['n']} ventes, part {D['extr25']['part']*100:.1f} %)")276 print(f"--- Réconciliation (ch. 20) : {D['indications']} ; é.-t. {D['ecarts_types20']} ; poids {D['poids20']} ; combinaison {_d2(D['V_raisonnee'])} (é.-t. {_d(D['sd_conclusion'])}) → CONCLUSION {_d(D['conclusion'])} ; fourchette {_d(D['fourchette'][0])} – {_d(D['fourchette'][1])} ; bande bootstrap 90 % {_d(D['bande90'][0])} – {_d(D['bande90'][1])}")277 print(f" résidu moyen retenus {D['residu_moyen_retenus']*100:.2f} % ; autres {D['residu_moyen_autres']*100:.2f} % ; k = 16 : {_d(D['V_16'])} ; vérité {_d(D['verite']['V_true'])} ; ratio prix/rôle {D['ratio_prix_role']:.3f}")278 print(D["tab20"].round(3).to_string(index=False))279 print(f"--- Mutation (Gatineau 2026, dès le 25 février) : base {_d(D['base_mutation'])} ; droit {_d2(D['droits_mutation'])} (barème général : {_d2(D['droits_mutation_general'])})")280 print("--- Financement (prix = valeur la plus probable) :")281 for k, f in D["financement"].items():282 print(f" {k:16s} mise {_d2(f['mise'])} ({f['mise_pct']*100:.2f} %) ; prêt {_d2(f['pret_base'])} RPV {f['rpv']*100:.2f} % ; prime {f['taux_prime']*100:.2f} % = {_d2(f['prime'])} (taxe hyp. 9 % {_d2(f['taxe_prime'])}) ; "283 f"prêt total {_d2(f['pret'])} (RPV {f['rpv_avec_prime']*100:.2f} %) ; taux {f['taux']*100:.2f} % ; {f['amortissement_mois']} mois ; paiement {_d2(f['paiement'])}/mois ; SD {_d2(f['service_dette'])} ; f {f['constante']*100:.3f} % ; "284 f"intérêts an 1 {_d2(f['interets_an1'])} ; capital an 1 {_d2(f['capital_an1'])} ; solde à 60 mois {_d2(f['solde_terme'])} ; MQR {f['mqr']*100:.2f} % → paiement {_d2(f['paiement_mqr'])} ; "285 f"revenu min ABD {_d(f['revenu_min_abd'])} ; ATD {_d(f['revenu_min_atd'])} ({f['contrainte_revenu']}) ; liquidités {_d2(f['liquidites'])}")286 print("--- Renouvellement (mise minimale, 25 ans) :\n", D["renouvellement"].round(4).to_string(index=False))287 print("--- Valeur d'emprunt du prêteur :", {k: {kk: round(vv) for kk, vv in v.items()} for k, v in D["valeur_emprunt"].items()})288 print("--- Exercices :")289 for k, v in exercices(D).items():290 print(" ", k, v if not isinstance(v, dict) else {kk: (round(vv, 4) if isinstance(vv, (int, float, np.floating)) else vv) for kk, vv in v.items()})291 return D292293294if __name__ == "__main__":295 main()296