Python 67%
TypeScript 18.2%
CSS 14.4%
1# -----------------------------------------------------------------------------2# Immo-Ka — Agrégateur de maisons à vendre (province de Québec)3# Auteur : Simon-Pierre Boucher — contact@spboucher.ai4# mortgage/cmhc.py : assurance prêt hypothécaire (SCHL / Sagen / Canada5# Guaranty). Règles et barèmes isolés ici pour être mis à jour facilement.6# Barème standard en vigueur (2026) — primes en % du prêt selon le7# rapport prêt-valeur (RPV).8# -----------------------------------------------------------------------------9from __future__ import annotations1011# (RPV maximal, prime en % du prêt)12PREMIUM_TABLE: list[tuple[float, float]] = [13 (0.65, 0.0060),14 (0.75, 0.0170),15 (0.80, 0.0240),16 (0.85, 0.0280),17 (0.90, 0.0310),18 (0.95, 0.0400),19]2021# Surprime pour amortissement assuré de 30 ans (premier acheteur / neuf,22# admissible depuis déc. 2024).23SURCHARGE_30Y = 0.00202425# Prix maximal admissible à l'assurance (porté de 1 M$ à 1,5 M$ en déc. 2024).26MAX_INSURABLE_PRICE = 1_500_0002728# Amortissement maximal assuré (30 ans seulement premier acheteur / neuf).29MAX_INSURED_AMORTIZATION = 2530MAX_INSURED_AMORTIZATION_FTHB = 303132# Taxe de vente du Québec sur la prime (payable comptant à la clôture,33# jamais ajoutée au prêt).34QC_TAX_ON_PREMIUM = 0.09975353637def min_down_payment(price: float) -> float:38 """Mise de fonds minimale légale au Canada.39 5 % de la première tranche de 500 000 $, 10 % de l'excédent jusqu'à40 1,5 M$ ; 20 % à compter de 1,5 M$."""41 if price <= 0:42 return 0.043 if price >= MAX_INSURABLE_PRICE:44 return round(0.20 * price, 2)45 return round(0.05 * min(price, 500_000) + 0.10 * max(0.0, price - 500_000), 2)464748def premium_rate(ltv: float, amort_years: int = 25) -> float | None:49 """Taux de prime selon le RPV. None si RPV > 95 % (non assurable)."""50 if ltv <= 0:51 return 0.052 for cap, rate in PREMIUM_TABLE:53 if ltv <= cap + 1e-9:54 extra = SURCHARGE_30Y if amort_years > 25 else 0.055 return rate + extra56 return None575859def insurance_quote(price: float, down_payment: float,60 amort_years: int = 25) -> dict:61 """Décomposition complète de l'assurance prêt hypothécaire.6263 Retourne : required (bool), eligible (bool), premium, premium_rate,64 loan_before, total_mortgage, qc_tax (payable comptant), ltv, issues[].65 Ne devine rien : si le scénario est inadmissible, le dit explicitement.66 """67 issues: list[str] = []68 if price <= 0 or down_payment < 0 or down_payment >= price:69 return {"required": False, "eligible": False, "premium": 0.0,70 "premium_rate": 0.0, "loan_before": max(price - down_payment, 0.0),71 "total_mortgage": max(price - down_payment, 0.0),72 "qc_tax": 0.0, "ltv": None, "issues": ["paramètres invalides"]}73 loan = round(price - down_payment, 2)74 ltv = loan / price75 required = ltv > 0.80 + 1e-976 if not required:77 return {"required": False, "eligible": True, "premium": 0.0,78 "premium_rate": 0.0, "loan_before": loan,79 "total_mortgage": loan, "qc_tax": 0.0,80 "ltv": round(ltv * 100, 2), "issues": issues}81 # Prêt assuré : vérifier l'admissibilité.82 eligible = True83 if price >= MAX_INSURABLE_PRICE:84 eligible = False85 issues.append("prix ≥ 1,5 M$ : assurance non disponible — mise de "86 "fonds de 20 % requise")87 if down_payment < min_down_payment(price) - 0.01:88 eligible = False89 issues.append("mise de fonds sous le minimum légal "90 f"({min_down_payment(price):,.0f} $)".replace(",", " "))91 if amort_years > MAX_INSURED_AMORTIZATION_FTHB:92 eligible = False93 issues.append("amortissement > 30 ans impossible pour un prêt assuré")94 elif amort_years > MAX_INSURED_AMORTIZATION:95 issues.append("30 ans assuré : réservé premier acheteur ou "96 "construction neuve (surprime de 0,20 %)")97 rate = premium_rate(ltv, amort_years) if eligible else None98 if rate is None and eligible:99 eligible = False100 issues.append("rapport prêt-valeur > 95 % : non assurable")101 premium = round(loan * rate, 2) if (eligible and rate) else 0.0102 return {103 "required": True,104 "eligible": eligible,105 "premium": premium,106 "premium_rate": round((rate or 0.0) * 100, 2),107 "loan_before": loan,108 "total_mortgage": round(loan + premium, 2),109 "qc_tax": round(premium * QC_TAX_ON_PREMIUM, 2),110 "ltv": round(ltv * 100, 2),111 "issues": issues,112 }113114115def allowed_amortizations(price: float, down_payment: float) -> list[int]:116 """Amortissements permis pour un scénario donné (25 ans max si assuré,117 30 ans si mise de fonds ≥ 20 % — ou premier acheteur/neuf assuré)."""118 if price <= 0:119 return [10, 15, 20, 25, 30]120 ltv = (price - down_payment) / price121 if ltv > 0.80:122 return [10, 15, 20, 25, 30] # 30 = cas particulier (signalé par issues)123 return [10, 15, 20, 25, 30]124