#!/usr/bin/env python3 """Figure ch21 — sensibilité du coût neuf du sujet du cas 1 (DONNÉES SYNTHÉTIQUES) : (a) grille taux d'indirects × taux d'incitatif ; (b) tornade (indirects, incitatif, indice de coûts). Sortie : assets/figures/ch21/ch21-fig-sensibilite.pdf""" import sys, pathlib ROOT = pathlib.Path(__file__).resolve().parents[3] sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) import numpy as np, matplotlib.pyplot as plt from traite.style import setup, palette, savefig, LARGEUR, fmt_dollars import ch21_cout_neuf as cas def main(): setup() devis, sujet = cas.charger(); cn = cas.cout_neuf_sujet(devis) ti = np.array([0.12, 0.15, 0.18, cn["taux_indirects"], 0.24, 0.27]); tc = np.array([0.05, 0.08, 0.10, 0.12, 0.15]) G = cas.grille_sensibilite(cn["direct"], ti, tc) ipcb = cas.ipcb_montreal(); mi = "Maison individuelle [621112]" var = cas.indice(ipcb, mi, "2026-07") / cas.indice(ipcb, mi, "2025-07") - 1 torn = cas.tornade(cn["direct"], cn["taux_indirects"], cas.TAUX_INCITATIF, (1 / (1 + var), 1 + var), (-0.05, -0.20))[:3] fig, (a, b) = plt.subplots(1, 2, figsize=(LARGEUR, 3.3), gridspec_kw={"width_ratios": [1.15, 1]}) im = a.imshow(G / 1000, cmap=palette.cmap_seq(), aspect="auto", origin="lower") a.set_xticks(range(len(tc))); a.set_xticklabels([f"{x*100:.0f} %" for x in tc]); a.set_yticks(range(len(ti))); a.set_yticklabels([f"{x*100:.1f} %" for x in ti]) a.set_xlabel("Taux d'incitatif (sur directs + indirects)"); a.set_ylabel("Taux d'indirects (sur directs)"); a.grid(False) for i in range(len(ti)): for j in range(len(tc)): a.text(j, i, f"{G[i, j]/1000:.0f}", ha="center", va="center", fontsize=7.5, color="white" if G[i, j] > G.mean() else palette.ink) a.set_title("(a) Coût neuf (milliers de $)", loc="left") base = torn[0]["base"] for k, o in enumerate(torn): lo, hi = min(o["bas"], o["haut"]), max(o["bas"], o["haut"]) b.barh(k, lo - base, left=base, color=palette.cat[0], height=0.55); b.barh(k, hi - base, left=base, color=palette.cat[1], height=0.55) b.text(lo - 1500, k, fmt_dollars(lo), ha="right", va="center", fontsize=7.2); b.text(hi + 1500, k, fmt_dollars(hi), ha="left", va="center", fontsize=7.2) b.set_yticks(range(len(torn))); b.set_yticklabels([f"{o['param']}\n({o['etiquette']})" for o in torn], fontsize=7.5) b.axvline(base, color=palette.ink, lw=0.8); b.set_xlim(base - 62000, base + 62000) b.xaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f"{x/1000:.0f} k$")); b.grid(axis="y", visible=False) b.set_title("(b) Tornade autour de " + fmt_dollars(base), loc="left") fig.tight_layout(w_pad=2.0) out = savefig(fig, "ch21/ch21-fig-sensibilite.pdf"); print("OK", out) if __name__ == "__main__": main()