SPB Git forge

spb/uqo-imm1033

Public
5commits 1branches 0releases
30.2 MBsize
maindefault branch
4 h agolast push
JavaScript 68% CSS 32%
27.5 KB · 388 lines javascript
Raw Blame History
1// outils-imm1033.js — calculateurs du cours IMM1033 (Méthodes du coût)2window.UQO_TOOLS = [3  {4    id: "formule",5    title: "Équation générale de la méthode du coût",6    ref: { label: "Aide-mémoire · formule fondamentale", url: "/aide-memoire/" },7    desc: "V = V<sub>T</sub> + (C<sub>N</sub> − D<sub>totale</sub>). Le terrain s’évalue séparément et ne se déprécie pas ; la dépréciation totale ventile physique, fonctionnelle et externe (améliorations seulement).",8    formula: "V = V<sub>T</sub> + (C<sub>N</sub> − D<sub>phys</sub> − D<sub>fonc</sub> − D<sub>ext</sub>)",9    inputs: [10      { key: "vt", label: "Valeur du terrain (vacant, à son PHMU)", default: 292000, unit: "$", step: 1000 },11      { key: "cn", label: "Coût neuf des améliorations", default: 1800000, unit: "$", step: 5000 },12      { key: "dphys", label: "Dépréciation physique (curable + CT + LT)", default: 889000, unit: "$", step: 1000 },13      { key: "dfonc", label: "Dépréciation fonctionnelle", default: 66000, unit: "$", step: 1000 },14      { key: "dext", label: "Dépréciation externe (améliorations seulement)", default: 84500, unit: "$", step: 500 },15      { key: "arr", label: "Arrondi de la conclusion", default: 10000, unit: "$", step: 5000 },16    ],17    compute(v) {18      const d = v.dphys + v.dfonc + v.dext;19      const amel = v.cn - d;20      const val = v.vt + amel;21      return [22        { label: "Dépréciation totale", value: d },23        { label: "Taux global de dépréciation (D ÷ C<sub>N</sub>)", value: d / v.cn * 100, fmt: "pct1", cls: d >= v.cn ? "warn" : "" },24        { label: "Valeur contributive des améliorations (C<sub>N</sub> − D)", value: amel, cls: amel < 0 ? "warn" : "good" },25        { label: "Part terrain / valeur totale", value: v.vt / val * 100, fmt: "pct1" },26        { label: "Valeur par la méthode du coût", value: val, cls: "main" },27        { label: "Conclusion arrondie", value: Math.round(val / v.arr) * v.arr },28        { label: d >= v.cn ? "⚠ La dépréciation totale ne peut excéder le coût neuf" : "Vérification : D<sub>totale</sub> < C<sub>N</sub>", value: d < v.cn ? "conforme" : "à revoir", fmt: "text", cls: d >= v.cn ? "warn" : "good" },29      ];30    },31  },32  {33    id: "terrain",34    title: "Terrain : comparaison directe (grille de 4 comparables)",35    ref: { label: "Séances 3–4 · évaluation du terrain", url: "/seance/04/" },36    desc: "Ajustement de marché (temps) appliqué en premier, puis ajustements de propriété additifs. On ajuste le comparable vers le sujet. Seuils de vraisemblance : net ≤ 15 %, brut ≤ 25 % ; les comparables les moins ajustés pèsent davantage.",37    formula: "P<sub>ajusté</sub> = P × (1 + a<sub>temps</sub>) × (1 + a<sub>loc</sub> + a<sub>sup</sub> + a<sub>zon</sub> + a<sub>topo</sub>)",38    inputs: [39      { key: "sup", label: "Superficie du terrain sujet", default: 8500, unit: "pi²", step: 100 },40      { key: "g", label: "Taux de variation du marché", default: 6, unit: "%/an", step: 0.5 },41      { group: "Comparable 1" },42      { key: "p1", label: "Prix", default: 259000, unit: "$", step: 1000 }, { key: "s1", label: "Superficie", default: 8000, unit: "pi²", step: 100 }, { key: "m1", label: "Mois écoulés", default: 6, unit: "mois", step: 1 }, { key: "a1", label: "Ajust. localisation + zonage + topo", default: -3, unit: "%", step: 0.5 },43      { group: "Comparable 2" },44      { key: "p2", label: "Prix", default: 285000, unit: "$", step: 1000 }, { key: "s2", label: "Superficie", default: 9800, unit: "pi²", step: 100 }, { key: "m2", label: "Mois écoulés", default: 10, unit: "mois", step: 1 }, { key: "a2", label: "Ajust. localisation + zonage + topo", default: 2, unit: "%", step: 0.5 },45      { group: "Comparable 3" },46      { key: "p3", label: "Prix", default: 241000, unit: "$", step: 1000 }, { key: "s3", label: "Superficie", default: 7200, unit: "pi²", step: 100 }, { key: "m3", label: "Mois écoulés", default: 3, unit: "mois", step: 1 }, { key: "a3", label: "Ajust. localisation + zonage + topo", default: 5, unit: "%", step: 0.5 },47      { group: "Comparable 4" },48      { key: "p4", label: "Prix", default: 272000, unit: "$", step: 1000 }, { key: "s4", label: "Superficie", default: 8900, unit: "pi²", step: 100 }, { key: "m4", label: "Mois écoulés", default: 14, unit: "mois", step: 1 }, { key: "a4", label: "Ajust. localisation + zonage + topo", default: -8, unit: "%", step: 0.5 },49    ],50    compute(v) {51      const rows = [];52      let wsum = 0, vsum = 0;53      const unit = [];54      for (let i = 1; i <= 4; i++) {55        const p = v["p" + i], s = v["s" + i];56        const at = v.g / 100 / 12 * v["m" + i];57        const pt = p * (1 + at);58        const upi2 = pt / s;59        // ajustement de superficie : la valeur unitaire décroît avec la taille (élasticité indicative −0,15)60        const aSup = -0.15 * (v.sup - s) / s;61        const aTot = v["a" + i] / 100 + aSup;62        const uAdj = upi2 * (1 + aTot);63        const net = at + aTot, brut = Math.abs(at) + Math.abs(v["a" + i] / 100) + Math.abs(aSup);64        const fiable = Math.abs(net) <= 0.15 && brut <= 0.25;65        const w = (fiable ? 1 : 0.3) / Math.max(brut, 0.02);66        wsum += w; vsum += w * uAdj;67        unit.push(uAdj);68        rows.push([`C${i}`, p, upi2, net * 100, brut * 100, uAdj]);69      }70      const u = vsum / wsum;71      const val = u * v.sup;72      return [73        { table: { head: ["Comp.", "Prix", "$/pi² (temps)", "Net", "Brut", "$/pi² ajusté"], rows, fmt: [null, "money", "money2", "pct1", "pct1", "money2"] } },74        { label: "Fourchette des $/pi² ajustés", value: `${new Intl.NumberFormat("fr-CA", { minimumFractionDigits: 2 }).format(Math.min(...unit))} – ${new Intl.NumberFormat("fr-CA", { minimumFractionDigits: 2 }).format(Math.max(...unit))} $/pi²`, fmt: "text" },75        { label: "Valeur unitaire réconciliée (pondérée par la fiabilité)", value: u, fmt: "money2" },76        { label: `Valeur du terrain sujet (${new Intl.NumberFormat("fr-CA").format(v.sup)} pi²)`, value: val, cls: "main" },77        { label: "Arrondie au 1 000 $", value: Math.round(val / 1000) * 1000 },78      ];79    },80  },81  {82    id: "extraction",83    title: "Terrain : extraction, affectation et résidu foncier",84    ref: { label: "Séance 3 · méthodes du terrain", url: "/seance/03/" },85    desc: "Trois méthodes d’appoint quand les ventes de terrains vacants manquent. L’extraction retranche la valeur dépréciée des améliorations d’une vente bâtie ; l’affectation applique un ratio de marché ; le résidu foncier capitalise le revenu résiduel du terrain.",86    formula: "V<sub>T</sub> = Prix − (C<sub>N</sub> − D) · V<sub>T</sub> = V<sub>totale</sub> × ratio · V<sub>T</sub> = (RNE − V<sub>bât</sub> × τ<sub>bât</sub>) ÷ τ<sub>terrain</sub>",87    inputs: [88      { group: "Extraction" },89      { key: "prix", label: "Prix de vente de la propriété bâtie", default: 1200000, unit: "$", step: 5000 },90      { key: "cn", label: "Coût neuf des améliorations", default: 1050000, unit: "$", step: 5000 },91      { key: "dep", label: "Dépréciation totale des améliorations", value: 0, default: 30, unit: "%", step: 1 },92      { group: "Affectation" },93      { key: "ratio", label: "Ratio terrain / valeur totale observé", default: 35, unit: "%", step: 1 },94      { group: "Résidu foncier" },95      { key: "rne", label: "RNE stabilisé de la propriété", default: 96000, unit: "$", step: 1000 },96      { key: "vb", label: "Valeur du bâtiment neuf au PHMU", default: 900000, unit: "$", step: 5000 },97      { key: "tb", label: "Taux de rendement du bâtiment (τ<sub>bât</sub>)", default: 8.5, unit: "%", step: 0.25 },98      { key: "tt", label: "Taux de rendement du terrain (τ<sub>terrain</sub>)", default: 6, unit: "%", step: 0.25 },99    ],100    compute(v) {101      const amel = v.cn * (1 - v.dep / 100);102      const ext = v.prix - amel;103      const aff = v.prix * v.ratio / 100;104      const revBat = v.vb * v.tb / 100;105      const revTer = v.rne - revBat;106      const res = revTer / (v.tt / 100);107      return [108        { label: "Valeur dépréciée des améliorations", value: amel },109        { label: "Terrain par extraction", value: ext, cls: "main" },110        { label: "Ratio terrain implicite (extraction)", value: ext / v.prix * 100, fmt: "pct1" },111        { label: "Terrain par affectation", value: aff },112        { label: "Revenu attribuable au bâtiment", value: revBat },113        { label: "Revenu résiduel du terrain", value: revTer, cls: revTer <= 0 ? "warn" : "" },114        { label: "Terrain par résidu foncier", value: res, cls: revTer <= 0 ? "warn" : "" },115        { label: "Écart extraction ↔ résidu", value: Math.abs(ext - res) / ext * 100, fmt: "pct1", cls: Math.abs(ext - res) / ext > 0.15 ? "warn" : "good" },116      ];117    },118  },119  {120    id: "harper",121    title: "Règle de profondeur 4-3-2-1 (Harper) et variante de Davis",122    ref: { label: "Définition · séance 4", url: "/seance/04/" },123    desc: "Répartit la valeur d’un terrain selon la profondeur : le premier quart (côté rue) porte 40 % de la valeur, puis 30 %, 20 % et 10 %. Utile pour ajuster un lot dont la profondeur s’écarte du standard du secteur.",124    formula: "Harper : 40-30-20-10 · Davis : 50-25-15-10 · valeur d’un lot = valeur standard × Σ des quarts couverts",125    inputs: [126      { key: "vstd", label: "Valeur du lot standard (pleine profondeur)", default: 180000, unit: "$", step: 1000 },127      { key: "pstd", label: "Profondeur standard du secteur", default: 120, unit: "pi", step: 5 },128      { key: "p", label: "Profondeur du lot sujet", default: 90, unit: "pi", step: 5 },129      { key: "regle", label: "Règle", type: "select", default: "harper", options: [{ value: "harper", label: "Harper 4-3-2-1" }, { value: "davis", label: "Davis 50-25-15-10" }] },130    ],131    compute(v) {132      const parts = v.regle === "davis" ? [0.5, 0.25, 0.15, 0.10] : [0.4, 0.3, 0.2, 0.1];133      const q = v.pstd / 4;134      let ratio = 0;135      const rows = [];136      for (let i = 0; i < 4; i++) {137        const couvert = Math.max(0, Math.min(q, v.p - i * q)) / q;138        ratio += couvert * parts[i];139        rows.push([`Quart ${i + 1} (${Math.round(i * q)}–${Math.round((i + 1) * q)} pi)`, parts[i] * 100, couvert * 100, couvert * parts[i] * 100]);140      }141      // au-delà de la profondeur standard : surplus valorisé à 5 % par quart (repère prudent)142      const extra = v.p > v.pstd ? Math.min(2, (v.p - v.pstd) / q) * 0.05 : 0;143      ratio += extra;144      return [145        { table: { head: ["Tranche", "Poids", "Couverte", "Contribution"], rows, fmt: [null, "pct1", "pct1", "pct1"] } },146        ...(extra ? [{ label: "Surplus de profondeur au-delà du standard (5 % par quart, max 2)", value: extra * 100, fmt: "pct1" }] : []),147        { label: "Ratio de valeur du lot sujet", value: ratio * 100, fmt: "pct1" },148        { label: "Valeur du lot sujet", value: v.vstd * ratio, cls: "main" },149        { label: "Valeur au pied linéaire de profondeur (sujet)", value: v.vstd * ratio / v.p, fmt: "money2" },150      ];151    },152  },153  {154    id: "coutneuf",155    title: "Coût neuf : directs, indirects, profit et indexation",156    ref: { label: "Séances 5–8 · coût de construction", url: "/seance/08/" },157    desc: "Le coût neuf total additionne les coûts directs, les coûts indirects (en % des directs) et le profit entrepreneurial (sur directs + indirects). L’indexation ramène un coût connu à la date d’évaluation par le ratio des indices (IPCB).",158    formula: "C<sub>N</sub> = C<sub>directs</sub> + C<sub>indirects</sub> + Profit · Profit = (directs + indirects) × taux · C<sub>cible</sub> = C<sub>connu</sub> × I<sub>cible</sub> / I<sub>connu</sub>",159    inputs: [160      { key: "sup", label: "Superficie brute", default: 15000, unit: "pi²", step: 100 },161      { key: "cu", label: "Coût direct unitaire (guide, base)", default: 160, unit: "$/pi²", step: 5 },162      { key: "mloc", label: "Multiplicateur régional (Gatineau ≈ 0,95, base Toronto 1,00)", default: 0.95, unit: "×", step: 0.01 },163      { key: "mq", label: "Multiplicateur de qualité / hauteur / périmètre (combiné)", default: 1.05, unit: "×", step: 0.01 },164      { key: "ind", label: "Coûts indirects", default: 19, unit: "% des directs", step: 1 },165      { key: "profit", label: "Profit entrepreneurial", default: 12, unit: "% de (directs + indirects)", step: 1 },166      { group: "Indexation (IPCB, base 2023 = 100)" },167      { key: "ibase", label: "Indice à la date du guide", default: 105.5, unit: "", step: 0.1 },168      { key: "icible", label: "Indice à la date d’évaluation", default: 108.7, unit: "", step: 0.1 },169    ],170    compute(v) {171      const directsBase = v.sup * v.cu * v.mloc * v.mq;172      const idx = v.icible / v.ibase;173      const directs = directsBase * idx;174      const indirects = directs * v.ind / 100;175      const profit = (directs + indirects) * v.profit / 100;176      const cn = directs + indirects + profit;177      return [178        { label: "Coûts directs (base guide × multiplicateurs)", value: directsBase },179        { label: "Facteur d’indexation I<sub>cible</sub> / I<sub>base</sub>", value: idx, fmt: "num4" },180        { label: "Coûts directs indexés", value: directs },181        { label: `Coûts indirects (${v.ind} %)`, value: indirects },182        { label: `Profit entrepreneurial (${v.profit} % sur directs + indirects)`, value: profit },183        { label: "Coût neuf total", value: cn, cls: "main" },184        { label: "Coût neuf au pi²", value: cn / v.sup, fmt: "money2" },185        { label: "Majoration totale au-dessus des directs", value: (cn / directs - 1) * 100, fmt: "pct1", cls: (cn / directs - 1) < 0.28 || (cn / directs - 1) > 0.45 ? "warn" : "good" },186      ];187    },188  },189  {190    id: "intercalaires",191    title: "Intérêts intercalaires et taxes de vente",192    ref: { label: "Séance 8 · coûts indirects", url: "/seance/08/" },193    desc: "Le financement de chantier se calcule sur les décaissements progressifs (facteur ½) ; les TPS/TVQ (14,975 %) s’ajoutent au coût seulement si elles ne sont pas récupérables (occupant, locatif résidentiel) — jamais pour un immeuble commercial à baux taxables.",194    formula: "I = C<sub>directs</sub> × i × T × ½ · Taxes = coût × 14,975 % (si non récupérables)",195    inputs: [196      { key: "cd", label: "Coûts directs", default: 1350000, unit: "$", step: 5000 },197      { key: "i", label: "Taux du prêt de construction (préférentiel + marge)", default: 6.45, unit: "%", step: 0.05 },198      { key: "t", label: "Durée du chantier", default: 14, unit: "mois", step: 1 },199      { key: "statut", label: "Statut fiscal", type: "select", default: "loc", options: [{ value: "occ", label: "Propriétaire-occupant (remboursements partiels)" }, { value: "loc", label: "Locatif résidentiel neuf (TPS remboursée à 100 %, TVQ partielle)" }, { value: "com", label: "Commercial — CTI/RTI (taxes non incluses)" }] },200    ],201    compute(v) {202      const I = v.cd * v.i / 100 * (v.t / 12) * 0.5;203      const tps = v.cd * 0.05, tvq = v.cd * 0.09975;204      let taxes;205      if (v.statut === "com") taxes = 0;206      else if (v.statut === "loc") taxes = tvq * 0.64; // remboursement TVQ partiel indicatif (36 %)207      else taxes = tps * 0.64 + tvq * 0.5;208      return [209        { label: "Intérêts intercalaires", value: I, cls: "main" },210        { label: "En % des coûts directs", value: I / v.cd * 100, fmt: "pct" },211        { label: "TPS brute (5 %)", value: tps },212        { label: "TVQ brute (9,975 %)", value: tvq },213        { label: "Taxes nettes à inclure au coût neuf (selon le statut)", value: taxes, cls: taxes ? "" : "good" },214        { label: "Repère : indirects usuels 15–25 % des directs", value: `${new Intl.NumberFormat("fr-CA").format(Math.round(v.cd * 0.15))} – ${new Intl.NumberFormat("fr-CA").format(Math.round(v.cd * 0.25))} $`, fmt: "text" },215      ];216    },217  },218  {219    id: "ventilation",220    title: "Ventilation de la dépréciation physique (curable, CT, LT)",221    ref: { label: "Séance 10 · dépréciation physique", url: "/seance/10/" },222    desc: "Ordre impératif : la curable (entretien différé) sort d’abord de la base ; les composantes à courte durée de vie se déprécient chacune selon leur âge/vie ; le résidu long terme (coût neuf − curable − coûts neufs CT) se déprécie au ratio âge effectif / vie économique du bâtiment.",223    formula: "D<sub>CT,j</sub> = (C<sub>neuf,j</sub> − curable<sub>j</sub>) × âge<sub>j</sub>/vie<sub>j</sub> · Base<sub>LT</sub> = C<sub>N</sub> − D<sub>cur</sub> − Σ C<sub>neuf,j</sub> · D<sub>LT</sub> = Base<sub>LT</sub> × âge/vie éco.",224    inputs: [225      { key: "cn", label: "Coût neuf total du bâtiment", default: 1800000, unit: "$", step: 5000 },226      { key: "cur", label: "Entretien différé (curable)", default: 45000, unit: "$", step: 1000 },227      { group: "Composante CT 1 — toiture" },228      { key: "c1", label: "Coût neuf", default: 120000, unit: "$", step: 1000 }, { key: "a1", label: "Âge effectif", default: 12, unit: "ans", step: 1 }, { key: "v1", label: "Durée de vie", default: 20, unit: "ans", step: 1 },229      { group: "Composante CT 2 — CVAC" },230      { key: "c2", label: "Coût neuf", default: 180000, unit: "$", step: 1000 }, { key: "a2", label: "Âge effectif", default: 15, unit: "ans", step: 1 }, { key: "v2", label: "Durée de vie", default: 22, unit: "ans", step: 1 },231      { group: "Composante CT 3 — finitions / planchers" },232      { key: "c3", label: "Coût neuf", default: 260000, unit: "$", step: 1000 }, { key: "a3", label: "Âge effectif", default: 9, unit: "ans", step: 1 }, { key: "v3", label: "Durée de vie", default: 15, unit: "ans", step: 1 },233      { group: "Long terme" },234      { key: "age", label: "Âge effectif global du bâtiment", default: 27, unit: "ans", step: 1 },235      { key: "vie", label: "Vie économique du bâtiment", default: 50, unit: "ans", step: 1 },236    ],237    compute(v) {238      const rows = [];239      let sumCT = 0, sumCneufCT = 0;240      for (let j = 1; j <= 3; j++) {241        const c = v["c" + j], a = v["a" + j], vie = v["v" + j];242        const ratio = Math.min(1, a / vie);243        const d = c * ratio;244        sumCT += d; sumCneufCT += c;245        rows.push([`CT ${j}`, c, ratio * 100, d]);246      }247      const baseLT = v.cn - v.cur - sumCneufCT;248      const ratioLT = Math.min(1, v.age / v.vie);249      const dLT = baseLT * ratioLT;250      const dphys = v.cur + sumCT + dLT;251      return [252        { table: { head: ["Composante", "Coût neuf", "Âge/vie", "Dépréciation"], rows, fmt: [null, "money", "pct1", "money"] } },253        { label: "Dépréciation curable (entretien différé)", value: v.cur },254        { label: "Σ dépréciation incurable court terme", value: sumCT },255        { label: "Base long terme (C<sub>N</sub> − curable − Σ coûts neufs CT)", value: baseLT, cls: baseLT <= 0 ? "warn" : "" },256        { label: `Dépréciation incurable long terme (${v.age}/${v.vie})`, value: dLT },257        { label: "Dépréciation physique totale", value: dphys, cls: "main" },258        { label: "En % du coût neuf", value: dphys / v.cn * 100, fmt: "pct1" },259        { label: "Vie économique restante du bâtiment", value: Math.max(0, v.vie - v.age), fmt: "int" },260        { label: "Comparaison : âge/vie simple (sans ventilation)", value: v.cn * ratioLT },261      ];262    },263  },264  {265    id: "fonctionnelle",266    title: "Dépréciation fonctionnelle : ajout, substitution, superadéquation, conception",267    ref: { label: "Séance 11 · dépréciation fonctionnelle", url: "/seance/11/" },268    desc: "Quatre gabarits de calcul avec garde-fous anti-double-comptage : la pénalité d’un ajout est l’excès de coût dans l’existant ; la superadéquation retranche la physique déjà comptée ; la conception capitalise la perte de loyer moins la portion terrain.",269    formula: "Ajout : D = coût dans l’existant − coût au neuf · Substitution : D = existant − neuf + démolition − récupération · Conception : D = perte de loyer ÷ TGA − portion terrain",270    inputs: [271      { group: "Déficience par ajout" },272      { key: "aje", label: "Coût d’installation dans l’existant", default: 38000, unit: "$", step: 500 },273      { key: "ajn", label: "Coût du même élément dans une construction neuve", default: 25000, unit: "$", step: 500 },274      { group: "Déficience par substitution" },275      { key: "sex", label: "Coût neuf de l’élément existant non encore déprécié", default: 13000, unit: "$", step: 500 },276      { key: "sdem", label: "Démolition de l’existant", default: 4000, unit: "$", step: 500 },277      { key: "srec", label: "Valeur de récupération", default: 500, unit: "$", step: 100 },278      { key: "ssur", label: "Surcoût d’installation dans l’existant", default: 6000, unit: "$", step: 500 },279      { group: "Superadéquation" },280      { key: "supc", label: "Excès de coût neuf de l’élément superadéquat", default: 80000, unit: "$", step: 1000 },281      { key: "supd", label: "Dépréciation physique déjà comptée sur cet excès", default: 27000, unit: "$", step: 1000 },282      { key: "supx", label: "Surcoûts d’exploitation annuels", default: 0, unit: "$/an", step: 100 },283      { group: "Obsolescence de conception" },284      { key: "perte", label: "Perte de loyer annuelle", default: 18000, unit: "$/an", step: 500 },285      { key: "tga", label: "TGA", default: 7, unit: "%", step: 0.25 },286      { key: "rt", label: "Portion attribuable au terrain", default: 20, unit: "%", step: 1 },287    ],288    compute(v) {289      const ajout = Math.max(0, v.aje - v.ajn);290      const subst = v.ssur + v.sdem - v.srec + v.sex;291      const supr = v.supc - v.supd + v.supx / (v.tga / 100);292      const concTot = v.perte / (v.tga / 100);293      const conc = concTot * (1 - v.rt / 100);294      return [295        { label: "Curable — déficience par ajout (excès de coût)", value: ajout },296        { label: "Curable — déficience par substitution", value: subst },297        { label: "Incurable — superadéquation (nette de la physique déjà comptée)", value: supr },298        { label: "Perte capitalisée (conception), avant allocation", value: concTot },299        { label: `Incurable — conception (améliorations, ${100 - v.rt} %)`, value: conc },300        { label: "Dépréciation fonctionnelle totale", value: ajout + subst + supr + conc, cls: "main" },301      ];302    },303  },304  {305    id: "externe",306    title: "Dépréciation externe : capitalisation, ventes appariées, allocation",307    ref: { label: "Séance 12 · dépréciation économique", url: "/seance/12/" },308    desc: "L’externe est toujours incurable et frappe terrain et améliorations : la dépréciation totale (perte de revenu ÷ TGA ou écart de ventes appariées) est allouée aux améliorations selon le ratio de valeur avant dépréciation. La portion terrain vit déjà dans V<sub>T</sub>.",309    formula: "D<sub>ext,totale</sub> = perte de RNE ÷ TGA · D<sub>ext,amél.</sub> = D<sub>totale</sub> × V<sub>amél.</sub> / V<sub>totale</sub>",310    inputs: [311      { key: "perte", label: "Perte de revenu net annuelle attribuable au facteur externe", default: 12000, unit: "$/an", step: 500 },312      { key: "tga", label: "TGA", default: 7, unit: "%", step: 0.25 },313      { key: "paires", label: "Écart moyen des ventes appariées (affecté vs non affecté)", default: 10, unit: "%", step: 0.5 },314      { key: "vprop", label: "Valeur de la propriété non affectée (pour la méthode des paires)", default: 1720000, unit: "$", step: 5000 },315      { key: "vt", label: "Valeur du terrain (avant dépréciation)", default: 292000, unit: "$", step: 1000 },316      { key: "vam", label: "Valeur des améliorations (avant dépréciation externe)", default: 845000, unit: "$", step: 1000 },317    ],318    compute(v) {319      const dcap = v.perte / (v.tga / 100);320      const dpaires = v.vprop * v.paires / 100;321      const ratioAm = v.vam / (v.vam + v.vt);322      return [323        { label: "Dépréciation externe totale — capitalisation de la perte", value: dcap },324        { label: "Dépréciation externe totale — ventes appariées", value: dpaires },325        { label: "Ratio améliorations / valeur totale (avant dépréciation)", value: ratioAm * 100, fmt: "pct1" },326        { label: "Externe allouée aux améliorations (capitalisation)", value: dcap * ratioAm, cls: "main" },327        { label: "Externe allouée aux améliorations (paires)", value: dpaires * ratioAm },328        { label: "Portion terrain (déjà reflétée dans V<sub>T</sub>, ne pas déduire)", value: dcap * (1 - ratioAm) },329        { label: "Écart entre les deux mesures", value: Math.abs(dcap - dpaires) / Math.max(dcap, dpaires) * 100, fmt: "pct1", cls: Math.abs(dcap - dpaires) / Math.max(dcap, dpaires) > 0.2 ? "warn" : "good" },330      ];331    },332  },333  {334    id: "equipements",335    title: "Équipements et améliorations locatives",336    ref: { label: "Séance 13 · applications spécialisées", url: "/seance/13/" },337    desc: "Équipement : âge/vie sur le coût installé (remplacement neuf + installation). Améliorations locatives : la durée de vie utile est le minimum de la vie physique et de la durée de bail restante — le bail limite presque toujours.",338    formula: "V<sub>équip.</sub> = (C<sub>rempl.</sub> + C<sub>install.</sub>) × (1 − âge/vie) · D<sub>loc.</sub> = âge ÷ min(vie physique, bail restant)",339    inputs: [340      { group: "Équipement" },341      { key: "cr", label: "Coût de remplacement neuf", default: 250000, unit: "$", step: 1000 },342      { key: "ci", label: "Coût d’installation", default: 35000, unit: "$", step: 1000 },343      { key: "ae", label: "Âge effectif", default: 8, unit: "ans", step: 1 },344      { key: "ve", label: "Vie économique", default: 20, unit: "ans", step: 1 },345      { group: "Améliorations locatives" },346      { key: "cal", label: "Coût neuf des améliorations locatives", default: 180000, unit: "$", step: 1000 },347      { key: "aal", label: "Âge", default: 3, unit: "ans", step: 1 },348      { key: "vph", label: "Vie physique", default: 15, unit: "ans", step: 1 },349      { key: "bail", label: "Durée de bail restante (options probables incluses)", default: 4, unit: "ans", step: 1 },350    ],351    compute(v) {352      const base = v.cr + v.ci;353      const ratio = Math.min(1, v.ae / v.ve);354      const veq = base * (1 - ratio);355      const vieUtile = Math.min(v.vph, v.aal + v.bail);356      const ratioAl = Math.min(1, v.aal / vieUtile);357      return [358        { label: "Coût installé (base)", value: base },359        { label: "Dépréciation âge/vie", value: ratio * 100, fmt: "pct1" },360        { label: "Valeur dépréciée de l’équipement", value: veq, cls: "main" },361        { label: "Vie utile retenue des améliorations locatives (min. physique, âge + bail)", value: vieUtile, fmt: "int" },362        { label: "Taux de dépréciation des améliorations locatives", value: ratioAl * 100, fmt: "pct1" },363        { label: "Valeur dépréciée des améliorations locatives", value: v.cal * (1 - ratioAl) },364        { label: "Dépréciation annuelle des améliorations locatives", value: v.cal / vieUtile },365      ];366    },367  },368  {369    id: "unites",370    title: "Conversions d’unités et coûts unitaires",371    ref: { label: "Aide-mémoire · conversion des unités", url: "/aide-memoire/" },372    desc: "Les guides de coûts publient en $/pi² ou $/m² ; ne mélangez jamais les deux dans un même bordereau.",373    formula: "1 m² ≈ 10,764 pi² · Coût ($/m²) = Coût ($/pi²) × 10,764",374    inputs: [375      { key: "m2", label: "Superficie", default: 1394, unit: "m²", step: 1 },376      { key: "cpi2", label: "Coût unitaire", default: 210, unit: "$/pi²", step: 5 },377    ],378    compute(v) {379      const pi2 = v.m2 * 10.7639;380      return [381        { label: "Superficie en pi²", value: pi2, fmt: "int" },382        { label: "Coût unitaire en $/m²", value: v.cpi2 * 10.7639, fmt: "money2", cls: "main" },383        { label: "Coût total", value: pi2 * v.cpi2 },384      ];385    },386  },387];388