spb/uqo-imm1003
Public
JavaScript 68%
CSS 32%
1// outils-imm1003.js — calculateurs du cours IMM1003 (Éléments d'évaluation immobilière)2// Chaque outil : { id, title, ref, desc, formula (HTML), inputs[], compute(v) → [{label, value, fmt, cls}] }3window.UQO_TOOLS = [4 {5 id: "rpv",6 title: "Ratio prêt/valeur et mise de fonds",7 ref: { label: "Formule 1.1 · séance 1", url: "/seance/01/#form-1-1" },8 desc: "Le prêteur retient la moindre du prix et de la valeur d’évaluation. Jusqu’à 80 % de RPV, le prêt est conventionnel ; au-delà, l’assurance prêt hypothécaire est obligatoire.",9 formula: "RPV = Prêt ÷ Valeur retenue × 100 · Valeur retenue = min(prix, évaluation)",10 inputs: [11 { key: "prix", label: "Prix d’achat accepté", default: 450000, unit: "$", step: 1000 },12 { key: "eval", label: "Valeur d’évaluation", default: 430000, unit: "$", step: 1000 },13 { key: "mise", label: "Mise de fonds prévue (en % du prix)", default: 20, unit: "%", step: 0.5 },14 { key: "rpvmax", label: "RPV maximal visé", default: 80, unit: "%", step: 1 },15 ],16 compute(v) {17 const retenue = Math.min(v.prix, v.eval);18 const pretMax = retenue * v.rpvmax / 100;19 const miseReq = v.prix - pretMax;20 const misePrev = v.prix * v.mise / 100;21 const pretPrev = v.prix - misePrev;22 const rpvPrev = pretPrev / retenue * 100;23 return [24 { label: "Valeur retenue par le prêteur (la moindre)", value: retenue },25 { label: `Prêt maximal à ${v.rpvmax} % de RPV`, value: pretMax },26 { label: "Mise de fonds requise (prix − prêt)", value: miseReq, cls: "main" },27 { label: "Mise de fonds prévue", value: misePrev },28 { label: "Écart à combler", value: miseReq - misePrev, cls: miseReq - misePrev > 0 ? "warn" : "good" },29 { label: "RPV effectif avec la mise prévue", value: rpvPrev, fmt: "pct1", cls: rpvPrev > 80 ? "warn" : "good" },30 { label: rpvPrev > 80 ? "Assurance prêt hypothécaire obligatoire (RPV > 80 %)" : "Prêt conventionnel (RPV ≤ 80 %)", value: rpvPrev > 80 ? "SCHL / Sagen / Canada Guaranty" : "aucune assurance requise", fmt: "text" },31 ];32 },33 },34 {35 id: "mutation",36 title: "Droits de mutation immobilière (barème 2026)",37 ref: { label: "Aide-mémoire · ratios", url: "/aide-memoire/" },38 desc: "Barème général par tranches, appliqué au plus élevé du prix payé et de la valeur au rôle pondérée. Les municipalités peuvent majorer les tranches au-delà de 500 000 $ (jusqu’à 3 %) ; Montréal a son propre barème.",39 formula: "0,5 % jusqu’à 62 900 $ · 1,0 % de 62 900 $ à 315 000 $ · 1,5 % au-delà (+ taux municipal facultatif au-delà de 500 000 $)",40 inputs: [41 { key: "base", label: "Base d’imposition (prix ou valeur au rôle pondérée, la plus élevée)", default: 477000, unit: "$", step: 1000 },42 { key: "t1", label: "Seuil 1", default: 62900, unit: "$", step: 100 },43 { key: "t2", label: "Seuil 2", default: 315000, unit: "$", step: 100 },44 { key: "muni", label: "Taux municipal majoré au-delà de 500 000 $ (0 = barème de base)", default: 0, unit: "%", step: 0.25, min: 0, max: 3 },45 ],46 compute(v) {47 const b = v.base;48 const tr1 = Math.min(b, v.t1) * 0.005;49 const tr2 = Math.max(0, Math.min(b, v.t2) - v.t1) * 0.01;50 let tr3, tr4 = 0;51 if (v.muni > 0 && b > 500000) {52 tr3 = Math.max(0, 500000 - v.t2) * 0.015;53 tr4 = (b - 500000) * v.muni / 100;54 } else {55 tr3 = Math.max(0, b - v.t2) * 0.015;56 }57 const total = tr1 + tr2 + tr3 + tr4;58 const out = [59 { label: `Tranche 1 — 0,5 % sur ${new Intl.NumberFormat("fr-CA").format(Math.min(b, v.t1))} $`, value: tr1, fmt: "money2" },60 { label: "Tranche 2 — 1,0 %", value: tr2, fmt: "money2" },61 { label: "Tranche 3 — 1,5 %", value: tr3, fmt: "money2" },62 ];63 if (tr4) out.push({ label: `Tranche municipale — ${v.muni} % au-delà de 500 000 $`, value: tr4, fmt: "money2" });64 out.push({ label: "Droits de mutation exigibles", value: total, fmt: "money2", cls: "main" });65 out.push({ label: "Taux effectif", value: total / b * 100, fmt: "pct" });66 return out;67 },68 },69 {70 id: "temporel",71 title: "Ajustement temporel d’un comparable",72 ref: { label: "Formule · séance 8", url: "/seance/08/" },73 desc: "Corrige le prix d’un comparable pour l’évolution du marché entre sa date de vente et la date d’évaluation. C’est un ajustement transactionnel, appliqué avant les ajustements physiques.",74 formula: "Ajustement = Prix × g<sub>mensuel</sub> × n<sub>mois</sub> (linéaire) ou Prix × (1 + g<sub>mensuel</sub>)<sup>n</sup> − Prix (composé)",75 inputs: [76 { key: "prix", label: "Prix de vente du comparable", default: 462000, unit: "$", step: 1000 },77 { key: "g", label: "Taux de variation du marché (annuel)", default: 6, unit: "%/an", step: 0.5 },78 { key: "mois", label: "Mois écoulés depuis la vente", default: 7, unit: "mois", step: 1, min: 0 },79 ],80 compute(v) {81 const gm = v.g / 100 / 12;82 const lin = v.prix * gm * v.mois;83 const comp = v.prix * Math.pow(1 + gm, v.mois) - v.prix;84 return [85 { label: "Taux mensuel équivalent", value: gm * 100, fmt: "pct3" },86 { label: "Ajustement linéaire (méthode du cours)", value: lin, cls: "main" },87 { label: "Prix ajusté (linéaire)", value: v.prix + lin },88 { label: "Ajustement composé (variante)", value: comp },89 { label: "Prix ajusté (composé)", value: v.prix + comp },90 { label: "Poids de l’ajustement dans le prix", value: lin / v.prix * 100, fmt: "pct", cls: Math.abs(lin / v.prix) > 0.1 ? "warn" : "" },91 ];92 },93 },94 {95 id: "grille",96 title: "Grille de comparaison — trois comparables",97 ref: { label: "Séance 9 · ajustements et réconciliation", url: "/seance/09/" },98 desc: "Saisissez le prix et les ajustements (du comparable vers le sujet : supérieur → négatif, inférieur → positif). L’outil calcule les prix ajustés, les ajustements net et brut, vérifie les seuils (net ≤ 15 %, brut ≤ 25 %) et propose une réconciliation pondérée par la fiabilité.",99 formula: "Prix ajusté = Prix × (1 + temps) + Σ ajustements $ · Net = Σ ajustements / prix · Brut = Σ |ajustements| / prix",100 inputs: [101 { group: "Comparable 1" },102 { key: "p1", label: "Prix", default: 445000, unit: "$", step: 1000 },103 { key: "t1", label: "Ajustement temporel", default: 2, unit: "%", step: 0.5 },104 { key: "a1", label: "Localisation", default: 0, unit: "$", step: 1000 },105 { key: "b1", label: "Superficie / physique", default: -12000, unit: "$", step: 1000 },106 { key: "c1", label: "Garage, sous-sol, extras", default: 8000, unit: "$", step: 1000 },107 { group: "Comparable 2" },108 { key: "p2", label: "Prix", default: 472000, unit: "$", step: 1000 },109 { key: "t2", label: "Ajustement temporel", default: 1, unit: "%", step: 0.5 },110 { key: "a2", label: "Localisation", default: -15000, unit: "$", step: 1000 },111 { key: "b2", label: "Superficie / physique", default: -8000, unit: "$", step: 1000 },112 { key: "c2", label: "Garage, sous-sol, extras", default: -10000, unit: "$", step: 1000 },113 { group: "Comparable 3" },114 { key: "p3", label: "Prix", default: 418000, unit: "$", step: 1000 },115 { key: "t3", label: "Ajustement temporel", default: 3.5, unit: "%", step: 0.5 },116 { key: "a3", label: "Localisation", default: 10000, unit: "$", step: 1000 },117 { key: "b3", label: "Superficie / physique", default: 15000, unit: "$", step: 1000 },118 { key: "c3", label: "Garage, sous-sol, extras", default: 12000, unit: "$", step: 1000 },119 ],120 compute(v) {121 const rows = [];122 const res = [];123 let wsum = 0, vsum = 0;124 for (let i = 1; i <= 3; i++) {125 const p = v["p" + i];126 const pt = p * (1 + v["t" + i] / 100);127 const adj = [v["a" + i], v["b" + i], v["c" + i]];128 const net = adj.reduce((a, b) => a + b, 0) + (pt - p);129 const brut = adj.reduce((a, b) => a + Math.abs(b), 0) + Math.abs(pt - p);130 const pa = pt + adj.reduce((a, b) => a + b, 0);131 const netPct = net / p * 100, brutPct = brut / p * 100;132 const fiable = Math.abs(netPct) <= 15 && brutPct <= 25;133 const w = fiable ? 1 / Math.max(brutPct, 1) : 0.25 / Math.max(brutPct, 1);134 wsum += w; vsum += w * pa;135 rows.push([`C${i}`, p, pa, netPct, brutPct, fiable ? "✓" : "⚠ hors seuil"]);136 }137 const concl = vsum / wsum;138 const arrondi = Math.round(concl / 5000) * 5000;139 const vals = rows.map((r) => r[2]);140 res.push({ table: { head: ["Comp.", "Prix", "Prix ajusté", "Net", "Brut", "Seuils"], rows, fmt: [null, "money", "money", "pct1", "pct1", "text"] } });141 res.push({ label: "Fourchette des indications", value: `${new Intl.NumberFormat("fr-CA").format(Math.round(Math.min(...vals)))} $ – ${new Intl.NumberFormat("fr-CA").format(Math.round(Math.max(...vals)))} $`, fmt: "text" });142 res.push({ label: "Écart de la fourchette", value: (Math.max(...vals) - Math.min(...vals)) / Math.min(...vals) * 100, fmt: "pct1", cls: (Math.max(...vals) - Math.min(...vals)) / Math.min(...vals) > 0.15 ? "warn" : "good" });143 res.push({ label: "Réconciliation pondérée par la fiabilité (poids ∝ 1/brut)", value: concl, cls: "main" });144 res.push({ label: "Conclusion arrondie (au 5 000 $)", value: arrondi });145 return res;146 },147 },148 {149 id: "rne",150 title: "Cascade des revenus : RBP → RBE → RNE → valeur",151 ref: { label: "Formules 11.x · séance 11", url: "/seance/11/" },152 desc: "Reconstruisez le revenu net d’exploitation d’un immeuble locatif puis capitalisez-le. Le service de la dette, l’amortissement et l’impôt sont exclus ; la gestion s’impute même en autogestion ; la réserve pour remplacement fait partie des dépenses.",153 formula: "RBE = RBP − inoccupation − mauvaises créances + autres revenus · RNE = RBE − dépenses · V = RNE ÷ TGA",154 inputs: [155 { key: "n", label: "Nombre de logements", default: 6, unit: "", step: 1, min: 1 },156 { key: "loyer", label: "Loyer mensuel moyen du marché", default: 1050, unit: "$/mois", step: 25 },157 { key: "autres", label: "Autres revenus annuels (stationnement, buanderie)", default: 2400, unit: "$/an", step: 100 },158 { key: "inocc", label: "Inoccupation et mauvaises créances", default: 4, unit: "% du RBP", step: 0.5 },159 { group: "Dépenses d’exploitation annuelles" },160 { key: "taxes", label: "Taxes municipales et scolaires", default: 9800, unit: "$", step: 100 },161 { key: "assur", label: "Assurances", default: 3600, unit: "$", step: 100 },162 { key: "entretien", label: "Entretien et réparations", default: 5200, unit: "$", step: 100 },163 { key: "energie", label: "Énergie (espaces communs) et déneigement", default: 3100, unit: "$", step: 100 },164 { key: "gestion", label: "Gestion", default: 5, unit: "% du RBE", step: 0.5 },165 { key: "reserve", label: "Réserve pour remplacement", default: 3, unit: "% du RBE", step: 0.5 },166 { group: "Capitalisation" },167 { key: "tga", label: "Taux global d’actualisation (TGA)", default: 5.5, unit: "%", step: 0.1 },168 ],169 compute(v) {170 const rbp = v.n * v.loyer * 12;171 const pertes = rbp * v.inocc / 100;172 const rbe = rbp - pertes + v.autres;173 const gestion = rbe * v.gestion / 100;174 const reserve = rbe * v.reserve / 100;175 const dep = v.taxes + v.assur + v.entretien + v.energie + gestion + reserve;176 const rne = rbe - dep;177 const ratio = dep / rbe * 100;178 const val = rne / (v.tga / 100);179 return [180 { label: "Revenu brut potentiel (RBP)", value: rbp },181 { label: `− Inoccupation et mauvaises créances (${v.inocc} %)`, value: -pertes },182 { label: "+ Autres revenus", value: v.autres },183 { label: "= Revenu brut effectif (RBE)", value: rbe, cls: "good" },184 { label: "Gestion", value: gestion },185 { label: "Réserve pour remplacement", value: reserve },186 { label: "− Dépenses d’exploitation totales", value: -dep },187 { label: "Ratio dépenses / RBE (repère 35–45 % pour 6–24 logements)", value: ratio, fmt: "pct1", cls: ratio < 30 || ratio > 50 ? "warn" : "good" },188 { label: "= Revenu net d’exploitation (RNE)", value: rne, cls: "good" },189 { label: `Valeur par capitalisation directe à ${v.tga} %`, value: val, cls: "main" },190 { label: "Valeur par logement", value: val / v.n },191 { label: "MRB implicite (valeur / RBE)", value: val / rbe, fmt: "num" },192 ];193 },194 },195 {196 id: "tga",197 title: "TGA : extraction, bande d’investissement et sommation",198 ref: { label: "Formules 12.x · séance 12", url: "/seance/12/" },199 desc: "Trois façons d’estimer le taux global d’actualisation : l’extraction sur une vente comparable (méthode reine), la bande d’investissement hypothèque–équité et la construction par sommation. Comparez-les.",200 formula: "TGA = RNE ÷ Prix · TGA = M·R<sub>m</sub> + (1 − M)·R<sub>e</sub> · R<sub>m</sub> = 12 × (i/12) / (1 − (1 + i/12)<sup>−n</sup>)",201 inputs: [202 { group: "Extraction du marché" },203 { key: "rne", label: "RNE du comparable", value: 0, default: 46800, unit: "$", step: 100 },204 { key: "prix", label: "Prix de vente du comparable", default: 851000, unit: "$", step: 1000 },205 { group: "Bande d’investissement" },206 { key: "m", label: "Ratio prêt/valeur (M)", default: 75, unit: "%", step: 1 },207 { key: "i", label: "Taux d’intérêt hypothécaire", default: 5.5, unit: "%", step: 0.05 },208 { key: "amort", label: "Amortissement", default: 25, unit: "ans", step: 1 },209 { key: "re", label: "Rendement exigé sur l’équité (R<sub>e</sub>)", default: 8, unit: "%", step: 0.25 },210 { group: "Sommation" },211 { key: "rf", label: "Taux sans risque (obligations 10 ans)", default: 3.2, unit: "%", step: 0.05 },212 { key: "prisque", label: "Prime de risque immobilier", default: 2.0, unit: "%", step: 0.1 },213 { key: "pliq", label: "Prime de non-liquidité", default: 1.0, unit: "%", step: 0.1 },214 { key: "pgest", label: "Prime de gestion", default: 0.5, unit: "%", step: 0.1 },215 { key: "g", label: "− Croissance anticipée des revenus", default: 1.5, unit: "%", step: 0.1 },216 ],217 compute(v) {218 const ext = v.rne / v.prix * 100;219 const im = v.i / 100 / 12;220 const n = v.amort * 12;221 const rm = 12 * im / (1 - Math.pow(1 + im, -n)) * 100;222 const bande = v.m / 100 * rm + (1 - v.m / 100) * v.re;223 const somm = v.rf + v.prisque + v.pliq + v.pgest - v.g;224 const vals = [ext, bande, somm];225 return [226 { label: "TGA extrait du marché (RNE ÷ prix)", value: ext, fmt: "pct", cls: "main" },227 { label: "Constante hypothécaire annuelle R<sub>m</sub>", value: rm, fmt: "pct" },228 { label: "TGA par bande d’investissement", value: bande, fmt: "pct" },229 { label: "TGA par sommation (taux construit)", value: somm, fmt: "pct" },230 { label: "Écart max. entre les trois méthodes", value: Math.max(...vals) - Math.min(...vals), fmt: "pct", cls: Math.max(...vals) - Math.min(...vals) > 1 ? "warn" : "good" },231 { label: "Multiplicateur de revenu net implicite (1/TGA extrait)", value: 100 / ext, fmt: "num" },232 ];233 },234 },235 {236 id: "dcf",237 title: "Actualisation des flux monétaires (DCF)",238 ref: { label: "Formule DCF · séance 12", url: "/seance/12/" },239 desc: "Valeur actuelle des RNE sur la période de détention plus la valeur de réversion (RNE de l’année n+1 capitalisé au TGA de sortie, moins les frais de vente). Observez le poids de la réversion et la sensibilité au taux.",240 formula: "V<sub>0</sub> = Σ RNE<sub>t</sub>/(1+r)<sup>t</sup> + V<sub>n</sub>/(1+r)<sup>n</sup> · V<sub>n</sub> = RNE<sub>n+1</sub> ÷ TGA<sub>sortie</sub> − frais de vente",241 inputs: [242 { key: "rne1", label: "RNE de l’année 1", default: 128000, unit: "$", step: 1000 },243 { key: "g", label: "Croissance annuelle du RNE", default: 2.5, unit: "%", step: 0.25 },244 { key: "n", label: "Période de détention", default: 5, unit: "ans", step: 1, min: 1, max: 15 },245 { key: "r", label: "Taux d’actualisation (r)", default: 7.5, unit: "%", step: 0.25 },246 { key: "tgaout", label: "TGA de sortie", default: 5.75, unit: "%", step: 0.25 },247 { key: "frais", label: "Frais de vente à la réversion", default: 3, unit: "% de la valeur brute", step: 0.5 },248 ],249 compute(v) {250 const r = v.r / 100, g = v.g / 100;251 let pv = 0;252 const rows = [];253 let rne = v.rne1;254 for (let t = 1; t <= v.n; t++) {255 const f = 1 / Math.pow(1 + r, t);256 pv += rne * f;257 rows.push([`Année ${t}`, rne, f, rne * f]);258 rne *= 1 + g;259 }260 const rneN1 = rne; // RNE année n+1261 const vBrute = rneN1 / (v.tgaout / 100);262 const vNette = vBrute * (1 - v.frais / 100);263 const pvRev = vNette / Math.pow(1 + r, v.n);264 const v0 = pv + pvRev;265 const capDirecte = v.rne1 / ((v.r - v.g) / 100);266 // sensibilité ±0,5 point sur r267 const sens = (dr) => { let s = 0, x = v.rne1; for (let t = 1; t <= v.n; t++) { s += x / Math.pow(1 + r + dr, t); x *= 1 + g; } return s + vNette / Math.pow(1 + r + dr, v.n); };268 return [269 { table: { head: ["Année", "RNE", "Facteur", "Valeur actuelle"], rows, fmt: [null, "money", "num4", "money"] } },270 { label: "VA des RNE de la période", value: pv },271 { label: `RNE de l’année ${v.n + 1} (base de la réversion)`, value: rneN1 },272 { label: "Valeur de réversion brute", value: vBrute },273 { label: "Valeur de réversion nette de frais", value: vNette },274 { label: "VA de la réversion", value: pvRev },275 { label: "Poids de la réversion dans la valeur", value: pvRev / v0 * 100, fmt: "pct1", cls: pvRev / v0 > 0.8 ? "warn" : "" },276 { label: "Valeur par DCF", value: v0, cls: "main" },277 { label: "Vérification : capitalisation directe au TGA ≈ r − g", value: capDirecte },278 { label: "Sensibilité : r + 0,5 point", value: sens(0.005) },279 { label: "Sensibilité : r − 0,5 point", value: sens(-0.005) },280 ];281 },282 },283 {284 id: "cout",285 title: "Méthode du coût simplifiée (âge/vie modifiée)",286 ref: { label: "Formules 10.x · séance 10", url: "/seance/10/" },287 desc: "V = coût neuf − dépréciation + terrain. L’entretien différé (curable) est retiré de la base, puis le ratio âge effectif / vie utile s’applique au reste ; les dépréciations fonctionnelle et économique s’ajoutent.",288 formula: "V = C<sub>neuf</sub> − D<sub>totale</sub> + V<sub>terrain</sub> · D<sub>phys. inc.</sub> = (C<sub>neuf</sub> − curable) × âge effectif / vie utile",289 inputs: [290 { key: "sup", label: "Superficie habitable", default: 1650, unit: "pi²", step: 10 },291 { key: "cu", label: "Coût direct unitaire", default: 165, unit: "$/pi²", step: 5 },292 { key: "ind", label: "Coûts indirects", default: 12, unit: "% des directs", step: 1 },293 { key: "profit", label: "Profit de l’entrepreneur", default: 10, unit: "% des directs", step: 1 },294 { key: "curable", label: "Entretien différé (curable)", default: 9500, unit: "$", step: 500 },295 { key: "age", label: "Âge effectif", default: 18, unit: "ans", step: 1, min: 0 },296 { key: "vie", label: "Vie utile économique", default: 60, unit: "ans", step: 5, min: 1 },297 { key: "fonc", label: "Dépréciation fonctionnelle", default: 6000, unit: "$", step: 500 },298 { key: "eco", label: "Dépréciation économique (externe)", default: 0, unit: "$", step: 500 },299 { key: "terrain", label: "Valeur du terrain (vacant, utilisation optimale)", default: 145000, unit: "$", step: 1000 },300 ],301 compute(v) {302 const directs = v.sup * v.cu;303 const indirects = directs * v.ind / 100;304 const profit = directs * v.profit / 100;305 const cneuf = directs + indirects + profit;306 const base = cneuf - v.curable;307 const inc = base * Math.min(1, v.age / v.vie);308 const dtot = v.curable + inc + v.fonc + v.eco;309 const val = cneuf - dtot + v.terrain;310 return [311 { label: "Coûts directs", value: directs },312 { label: "Coûts indirects", value: indirects },313 { label: "Profit de l’entrepreneur", value: profit },314 { label: "Coût à neuf", value: cneuf, cls: "good" },315 { label: "Dépréciation physique curable (entretien différé)", value: v.curable },316 { label: `Dépréciation physique incurable (${v.age}/${v.vie} de la base résiduelle)`, value: inc },317 { label: "Dépréciation fonctionnelle + économique", value: v.fonc + v.eco },318 { label: "Dépréciation totale", value: dtot },319 { label: "Taux global de dépréciation", value: dtot / cneuf * 100, fmt: "pct1", cls: dtot >= cneuf ? "warn" : "" },320 { label: "Valeur dépréciée des améliorations", value: cneuf - dtot },321 { label: "+ Valeur du terrain", value: v.terrain },322 { label: "Valeur par la méthode du coût", value: val, cls: "main" },323 { label: "Arrondie au 5 000 $", value: Math.round(val / 5000) * 5000 },324 ];325 },326 },327 {328 id: "reconciliation",329 title: "Réconciliation pondérée des trois méthodes",330 ref: { label: "Séance 14 · réconciliation", url: "/seance/14/" },331 desc: "La conclusion n’est jamais une moyenne : chaque indication est pondérée selon la pertinence de la méthode, la qualité des données et le comportement des acheteurs types. Les poids doivent totaliser 100 %.",332 formula: "V<sub>conclue</sub> = Σ w<sub>i</sub> × V<sub>i</sub>, avec Σ w<sub>i</sub> = 1",333 inputs: [334 { key: "vc", label: "Indication — comparaison", default: 1580000, unit: "$", step: 5000 },335 { key: "wc", label: "Poids — comparaison", default: 40, unit: "%", step: 5 },336 { key: "vk", label: "Indication — coût", default: 1610000, unit: "$", step: 5000 },337 { key: "wk", label: "Poids — coût", default: 15, unit: "%", step: 5 },338 { key: "vr", label: "Indication — revenu", default: 1580000, unit: "$", step: 5000 },339 { key: "wr", label: "Poids — revenu", default: 45, unit: "%", step: 5 },340 { key: "arr", label: "Arrondi", default: 5000, unit: "$", step: 1000 },341 ],342 compute(v) {343 const w = v.wc + v.wk + v.wr;344 const val = (v.vc * v.wc + v.vk * v.wk + v.vr * v.wr) / 100;345 const moy = (v.vc + v.vk + v.vr) / 3;346 const vals = [v.vc, v.vk, v.vr];347 return [348 { label: "Somme des poids", value: w, fmt: "pct1", cls: Math.abs(w - 100) > 0.01 ? "warn" : "good" },349 { label: "Valeur pondérée", value: val * 100 / w, cls: "main" },350 { label: `Conclusion arrondie (${new Intl.NumberFormat("fr-CA").format(v.arr)} $)`, value: Math.round(val * 100 / w / v.arr) * v.arr },351 { label: "Moyenne simple (à titre indicatif seulement — jamais la conclusion)", value: moy },352 { label: "Dispersion des indications (max − min) / min", value: (Math.max(...vals) - Math.min(...vals)) / Math.min(...vals) * 100, fmt: "pct1", cls: (Math.max(...vals) - Math.min(...vals)) / Math.min(...vals) > 0.1 ? "warn" : "good" },353 ];354 },355 },356 {357 id: "tvm",358 title: "Valeur temps de l’argent",359 ref: { label: "Formules VF / VA · séance 12", url: "/seance/12/" },360 desc: "Un dollar reçu aujourd’hui vaut plus qu’un dollar reçu demain. Capitalisation d’une somme, actualisation d’un montant futur et constante hypothécaire.",361 formula: "FV = PV × (1 + r)<sup>n</sup> · PV = FV ÷ (1 + r)<sup>n</sup>",362 inputs: [363 { key: "pv", label: "Montant présent (PV)", default: 100000, unit: "$", step: 1000 },364 { key: "fv", label: "Montant futur à actualiser (FV)", default: 150000, unit: "$", step: 1000 },365 { key: "r", label: "Taux annuel", default: 5, unit: "%", step: 0.25 },366 { key: "n", label: "Nombre d’années", default: 10, unit: "ans", step: 1, min: 0 },367 ],368 compute(v) {369 const r = v.r / 100;370 const fv = v.pv * Math.pow(1 + r, v.n);371 const pv = v.fv / Math.pow(1 + r, v.n);372 const doubling = Math.log(2) / Math.log(1 + r);373 return [374 { label: `Valeur future de ${new Intl.NumberFormat("fr-CA").format(v.pv)} $ dans ${v.n} ans`, value: fv, cls: "main" },375 { label: `Valeur présente de ${new Intl.NumberFormat("fr-CA").format(v.fv)} $ reçus dans ${v.n} ans`, value: pv },376 { label: "Facteur de capitalisation (1 + r)ⁿ", value: Math.pow(1 + r, v.n), fmt: "num4" },377 { label: "Facteur d’actualisation 1/(1 + r)ⁿ", value: 1 / Math.pow(1 + r, v.n), fmt: "num4" },378 { label: "Temps de doublement au taux r", value: doubling, fmt: "num" },379 ];380 },381 },382 {383 id: "unites",384 title: "Conversions d’unités et prix unitaires",385 ref: { label: "Aide-mémoire · conversions", url: "/aide-memoire/" },386 desc: "Pi² ↔ m², acres ↔ hectares, et prix unitaires cohérents entre les systèmes.",387 formula: "1 m² ≈ 10,764 pi² · 1 acre ≈ 43 560 pi² ≈ 4 047 m² · 1 ha = 10 000 m²",388 inputs: [389 { key: "pi2", label: "Superficie", default: 1650, unit: "pi²", step: 10 },390 { key: "prix", label: "Prix total", default: 477000, unit: "$", step: 1000 },391 { key: "acres", label: "Terrain", default: 0.5, unit: "acre", step: 0.05 },392 ],393 compute(v) {394 const m2 = v.pi2 / 10.7639;395 return [396 { label: "Superficie en m²", value: m2, fmt: "num" },397 { label: "Prix au pi²", value: v.prix / v.pi2, fmt: "money2", cls: "main" },398 { label: "Prix au m²", value: v.prix / m2, fmt: "money2" },399 { label: "Terrain en pi²", value: v.acres * 43560, fmt: "int" },400 { label: "Terrain en m²", value: v.acres * 4046.86, fmt: "int" },401 { label: "Terrain en hectares", value: v.acres * 0.404686, fmt: "num4" },402 ];403 },404 },405];406